api-layer
67 beliefs (53 IN, 14 OUT)
The api-layer topic covers the design and behavioral contracts of the two outermost layers in the reasons system: the functional API in api.py and the CLI in cli.py. These layers mediate all access to the underlying TMS engine and persistence, so their contracts determine what callers can rely on — JSON-serializability of returns, atomicity of mutations, error semantics, and search resilience. The three-layer architecture belief (three-layer-architecture) establishes the overall structure: data model, TMS engine, and persistence at the bottom, with the API providing context-managed operations and the CLI acting as a thin formatting wrapper on top.
The API layer enforces several interlocking safety properties. Every mutation runs inside a context manager that guarantees atomic load-operate-save semantics (api-uses-with-network-context-manager, api-layer-ensures-atomic-isolated-mutations), and mutating operations use before/after truth-value diffing to report structured change sets (api-mutating-ops-use-before-after-diffing). Public functions return dicts rather than live objects (api-functions-return-dicts), preventing callers from holding references that could bypass the persistence boundary. At the access control boundary, single-node lookups raise PermissionError on forbidden nodes while collection endpoints silently filter (direct-access-raises-list-access-filters, single-node-api-raises-permissionerror). Input validation catches duplicates via ValueError (duplicate-node-id-raises-valueerror) and requires at least one justification type on add_justification (api-add-justification-requires-justification-arg). Search is notably resilient: it tries FTS5 first, falls back to substring matching, and caps progressive relaxation at 50 queries to prevent combinatorial blowup (search-uses-fts5-with-substring-fallback, search-relaxation-capped-at-50-queries, api-fts-search-bounds-query-calls). Both layers use lazy imports to keep startup fast (api-uses-lazy-imports, cli-uses-lazy-imports-for-heavy-modules).
The CLI layer is characterized as a pure formatter: every cmd_* handler delegates to an api.* function and only formats the returned dict for terminal display (cli-is-pure-formatter). Dispatch is a flat dictionary lookup with no plugin system (cli-dispatch-is-flat-dict-lookup), exit codes are strictly binary — 0 or 1 (cli-exit-code-contract-is-binary), and output streams are cleanly separated with diagnostics on stderr and results on stdout (cli-errors-use-stderr-success-uses-stdout). Several commands follow a plan-review-apply workflow where proposals are written to a file for human review before being applied (cli-plan-review-apply-pattern). A notable subset of commands — those requiring filesystem access, LLM calls, or bulk load-modify-save semantics — are SQLite-only and will refuse to run against PostgreSQL (cli-sqlite-only-commands-exist, sqlite-only-commands-are-filesystem-llm-or-bulk). Testing is black-box integration: every test creates an isolated database and exercises the full argv-parsing pipeline (cli-tests-are-black-box-integration, each-cli-test-creates-isolated-db).
Several beliefs are retracted (OUT), and these mostly represent higher-order synthesis claims that were superseded or found to overreach. For example, api-enforces-typed-preconditions, cli-is-pure-delegation-layer, and cli-is-verified-pure-delegation were retracted, likely because their sweeping characterizations conflicted with specific exceptions like cmd_propagate bypassing the API (cmd-propagate-bypasses-api, itself now also OUT). Similarly, a cluster of derived beliefs about mutation pipeline properties (mutation-pipeline-is-atomic-snapshot, mutation-pipeline-produces-consistent-state, mutations-are-atomic-and-safely-propagated, mutation-safety-spans-all-dimensions) are all OUT, suggesting that these broad unifying claims were retracted in favor of more precise, individually scoped beliefs like mutations-are-atomic-audited-and-index-consistent and mutations-are-observable-audited-and-index-consistent which remain IN. The pattern of retraction indicates an epistemic tightening — replacing ambitious synthesized claims with narrower, directly verifiable ones.
-
IN
api-add-justification-requires-justification-arg
`api.add_justification` raises `ValueError` if none of `sl`, `cp`, or `unless` is provided — at least one justification specification is mandatory. -
IN
api-cascade-symmetry-tested
Test coverage claim; the underlying behavioral invariant (symmetric retract/restore cascades) is already covered by existing beliefs including `reasoning-engine-is-deterministic-and-reversible`. -
OUT
api-enforces-typed-preconditions
API functions enforce preconditions at the system boundary with typed exceptions: duplicate node IDs raise ValueError, missing justification arguments raise ValueError, and unauthorized single-node access raises PermissionError — establishing a consistent error contract at every entry point. -
IN
api-fts-search-bounds-query-calls
`_fts_search` makes at most 51 internal `_fts_query` calls regardless of input query length, preventing combinatorial explosion from progressive relaxation on long queries. -
IN
api-functions-return-dicts
Every public API function returns a `dict` (or `str` for markdown/compact), never a `Network` or `Node` object, ensuring JSON-serializability at the boundary for CLI, HTTP, and tool-call consumers. -
IN
api-idempotent-retract-assert
Already exists as an accepted belief with the same ID and content. -
IN
api-layer-ensures-atomic-isolated-mutations
The API layer enforces mutation safety through four mechanisms: context-managed load/save, per-function transaction scope, write-flag gating to prevent unintended persistence, and dict-only returns that prevent callers from holding live network references. -
IN
api-list-negative-filters-hallucinated-ids
`list_negative()` discards any node IDs returned by the LLM that don't exist in the database, preventing hallucinated IDs from appearing in results. -
IN
api-list-negative-graceful-on-malformed-llm
When the LLM returns unparseable output, `list_negative()` returns `count == 0` rather than raising an exception — graceful degradation over failure. -
IN
api-mutating-ops-use-before-after-diffing
Mutating operations (`retract_node`, `assert_node`, `what_if_retract`, `what_if_assert`) snapshot all truth values before the operation and diff afterward to classify changes into `went_out`/`went_in` lists. -
IN
api-retract-cascade-is-transitive
`api.retract_node()` propagates OUT to all transitively dependent SL-derived nodes, not just direct children — retracting a root premise retracts the entire downstream chain. -
IN
api-tests-black-box
Test methodology claim, not a behavioral invariant about the codebase. Developers learn this from reading the tests, not from a belief registry. -
IN
api-tests-cover-subset
Test coverage inventory, not a behavioral claim about the codebase. Coverage gaps are better tracked as project-level issues. -
IN
api-tests-use-real-sqlite
All API tests run against a real SQLite database with FTS5; storage is never mocked, ensuring the API contract includes correct SQL and full-text search behavior. -
IN
api-uses-lazy-imports
Heavy modules (`derive`, `compact`, `export_markdown`, `check_stale`, `import_beliefs`, `import_agent`) are imported inside function bodies in `api.py`, not at module level, to keep the module fast to import for callers that only need a subset of operations. -
IN
api-uses-with-network-context-manager
`api.py` uses a `_with_network` context manager to ensure load-operate-save atomicity for all network mutations. -
IN
cli-backend-kwargs-controls-storage
`_backend_kwargs(args)` is the single chokepoint that determines whether a command runs against SQLite or PostgreSQL; every `cmd_*` function must spread its return value into the corresponding `api.*` call -
IN
cli-dispatch-is-flat-dict-lookup
CLI dispatch uses a flat `commands` dict mapping subcommand strings to `cmd_*` handler functions — no plugin system or subclass hierarchy. -
IN
cli-errors-use-stderr-success-uses-stdout
CLI error diagnostics are written to stderr and success output to stdout; tests consistently assert on the correct stream. -
IN
cli-exit-1-on-error
All CLI error paths catch specific exceptions from the API (`KeyError`, `ValueError`, `PermissionError`, `FileNotFoundError`), print to stderr, and call `sys.exit(1)`. -
IN
cli-exit-code-contract-is-binary
Every CLI subcommand returns exit code 0 for success and 1 for any user-facing error; no other exit codes are used or tested. -
IN
cli-flags-override-env-vars
`_backend_kwargs` gives precedence to CLI `--pg`/`--project-id` flags over `REASONS_PG_CONNINFO`/`REASONS_PROJECT_ID` environment variables when both are present -
IN
cli-is-deterministic-and-stream-correct
The CLI achieves full scriptability through three deterministic properties: flat dict dispatch with no dynamic plugin resolution, binary exit codes (0 success, 1 error) with no ambiguous intermediate codes, and clean stream separation (diagnostics to stderr, results to stdout) -
OUT
cli-is-pure-delegation-layer
The CLI is a pure delegation layer: every handler dispatches through a flat dict lookup to API functions with no business logic, producing binary exit codes and correct stream separation — a complete separation of formatting from computation. -
IN
cli-is-pure-formatter
Every cmd_* function delegates to api.* and only formats the returned dict for terminal output; no business logic lives in the CLI layer. -
IN
cli-is-verified-end-to-end
The CLI is verified through hermetic end-to-end integration tests (isolated databases per test, full argv-parsing pipeline, deterministic stream-correct output) — unless cmd_propagate bypasses the API layer, leaving one code path's safety guarantees unverifiable through the standard integration testing harness. -
OUT
cli-is-verified-pure-delegation
The CLI is both structurally pure (every handler delegates to API functions with no business logic) and end-to-end verified (hermetic integration tests confirm delegation produces correct output through the full argv-parsing pipeline). -
IN
cli-plan-review-apply-pattern
Several commands (`derive`, `deduplicate`, `contradictions`) follow a three-phase workflow: (1) generate proposals to a file, (2) human reviews/edits the file, (3) `--accept FILE` parses and applies the reviewed plan — with `--auto` collapsing all three phases -
IN
cli-sqlite-only-commands-exist
Commands `derive`, `ask`, `review-beliefs`, `deduplicate`, and `contradictions` are guarded by `_require_sqlite()` and exit with an error if `--pg` is set — they do not support PostgreSQL -
IN
cli-tests-are-black-box-integration
All CLI tests invoke `main()` through the full argv-parsing pipeline via the `run_cli` harness rather than calling internal APIs, with one exception (`TestPropagateWithChanges` directly mutates storage to create inconsistent state). -
IN
cli-uses-lazy-imports-for-heavy-modules
`asyncio`, `derive`, `ask`, and `Storage` are imported inside function bodies rather than at module level, keeping `reasons --help` fast. -
OUT
cmd-propagate-bypasses-api
`cmd_propagate` is the only CLI handler that bypasses `api.py`, going directly to `Storage` → `Network.recompute_all()` → `Storage.save()` — a design inconsistency in the otherwise pure-presentation CLI layer. -
IN
colon-means-already-namespaced
`_resolve_namespace` treats a colon in a node ID as "already namespaced" and never double-prefixes; this is the convention for cross-namespace references. -
IN
commands-dict-must-mirror-subparsers
Adding a CLI subcommand requires entries in both the argparse subparser definitions and the `commands` dispatch dict in `main()`; omitting either silently breaks the command. -
IN
direct-access-raises-list-access-filters
API functions for single-node access (`show_node`, `explain_node`, `trace_assumptions`) raise `PermissionError` on forbidden nodes, while list/export functions (`list_nodes`, `search`, `export_network`) silently exclude them from results. -
IN
duplicate-node-id-raises-valueerror
`api.add_node()` raises `ValueError` when given a node ID that already exists in the network — node IDs are unique. -
IN
each-cli-test-creates-isolated-db
Every CLI test method initializes a fresh SQLite database via `run_cli("init")` in a pytest `tmp_path`, ensuring zero shared state between tests. -
IN
entry-point-mapping
The `reasons` CLI command maps to `reasons_lib.cli:main` via `[project.scripts]` in `pyproject.toml` and is the only registered script entry point. -
OUT
every-mutation-reports-its-effects
All mutating operations report their effects as structured data: retract returns the full changed set, add_justification returns a change dict with old/new truth values, and API mutating operations use before/after truth-value diffing to capture deltas. -
IN
init-db-refuses-existing-without-force
`api.init_db()` raises `FileExistsError` when the database file already exists, unless `force=True` is passed to allow overwrite. -
OUT
input-validation-is-comprehensive-at-all-boundaries
Input validation is enforced at every system boundary through complementary mechanisms: typed exceptions (ValueError for duplicates, PermissionError for access violations) enforce API-level preconditions at the call boundary, while defense-in-depth reference validation (import normalization dropping unknown refs, nogood filtering skipping invalid nodes, hallucinated ID rejection) catches invalid node references at every data-acceptance boundary. -
OUT
mutation-pipeline-is-atomic-snapshot
Every network mutation follows an atomic snapshot pipeline: API context management ensures load/save atomicity with write-flag gating, while storage performs full-replace persistence — no partial state is ever visible between operations. -
OUT
mutation-pipeline-produces-consistent-state
Every mutation produces a fully consistent persisted network: atomic load/save ensures no partial writes, deterministic propagation ensures all truth values are correctly derived, and lifecycle-aware traversal prevents stale recomputations. -
OUT
mutation-safety-spans-all-dimensions
Every mutation is safe along three orthogonal dimensions simultaneously: source dimension (human, LLM, or agent), semantic dimension (uniform revision via outlist defeat and backtracking), and lifecycle dimension (respects retraction state and propagation bounds) — no mutation path is exempt from any dimension. -
OUT
mutations-are-atomic-and-safely-propagated
Every network mutation follows an end-to-end safety pipeline: API context management ensures atomic load/save with write-flag gating, truth propagation terminates deterministically with lifecycle-aware BFS traversal, and snapshot persistence captures the final consistent state — no mutation can produce an inconsistent or divergent network. -
IN
mutations-are-atomic-audited-and-index-consistent
Every network mutation achieves three simultaneous guarantees: transactional atomicity (context-managed load/save with write-flag gating), historical auditability (timestamped audit log entries), and structural consistency (dependents index updated synchronously) — forming a complete mutation-safety contract. -
IN
mutations-are-observable-audited-and-index-consistent
Every network mutation achieves triple-layered traceability: callers receive structured before/after diffs at the API level, the internal audit log records timestamped events for historical analysis, and the dependents index is simultaneously maintained — providing both external and internal observability. -
IN
network-add-node-rejects-duplicates
`Network.add_node()` raises `ValueError` if a node with the given ID already exists in the network. -
OUT
output-governance-is-complete-authorized-and-ci-ready
All system output is simultaneously structurally complete (priority-ordered compact summaries with predictable bounds), authorized (access-tag subset gating with transitive inheritance), resource-constrained (accurate bidirectional token budgets), and machine-parseable (deterministic CI-ready staleness reports with nonzero exit codes). -
OUT
output-governance-is-comprehensive-and-self-sustaining
All system output is simultaneously comprehensively governed (normalized schemas with deterministic structure, authorized via access-tag subset gating, resource-constrained via token budgets) AND self-sustaining in quality (knowledge freshness actively maintained through staleness detection feeding back into the pipeline, query resilience ensuring graceful degradation across all access paths) — output governance is not a static structural property but an actively maintained dynamic one. -
IN
reasons-cli-entrypoint-is-cli-main
The `reasons` CLI command is registered as `reasons_lib.cli:main` via `[project.scripts]` in pyproject.toml — changing that function's signature or module location breaks the installed command. -
IN
run-cli-helper-catches-systemexit
The `run_cli` test harness intercepts `SystemExit` to extract exit codes, preventing argparse errors or explicit `sys.exit()` calls from terminating the test process. -
IN
search-falls-back-to-substring
`api.search()` tries FTS5 full-text search first, then falls back to substring matching if FTS5 tables don't exist or error; it always produces results if substring matches exist. -
IN
search-has-four-output-formats
The `search` function supports four output formats: markdown, json, minimal, and compact — selected by the caller to match the consumption context (human, API, LLM prompt, compact view). -
IN
search-is-resilient-across-index-states
Search operates correctly regardless of FTS5 index availability: the index is derived (rebuilt from scratch on every save) so stale indexes are self-healing, and search falls back to substring matching when FTS tables don't exist or error -
IN
search-relaxation-capped-at-50-queries
FTS progressive relaxation drops terms via `combinations()` (largest subsets first) until results appear, capped at 50 total relaxation queries to prevent combinatorial explosion on long search strings. -
IN
search-source-chunks-filters-stop-words
`_search_source_chunks` filters out single-character tokens and common stop words before constructing FTS5 queries, returning empty string when no usable terms remain. -
IN
search-uses-fts5-with-substring-fallback
`api.search()` tries FTS5 first (`_fts_search`), falls back to substring matching (`_substring_search`), then expands results with 1-hop neighbors from the dependency graph. -
IN
single-cli-entry-point
The only registered console script is `reasons`, pointing to `reasons_lib.cli:main`; all subcommands are dispatched internally -
IN
single-node-api-raises-permissionerror
API functions that target a single node by ID (`show_node`, `explain_node`, `trace_assumptions`, `trace_access_tags`) raise `PermissionError` when the caller lacks clearance; collection endpoints silently filter instead. -
IN
sqlite-only-commands-are-filesystem-llm-or-bulk
The ~21 SQLite-only CLI commands fall into three categories: filesystem-dependent (hash-sources, check-stale, add-repo), LLM-powered (derive, review-beliefs, detect-contradictions, ask), and bulk import/sync — all requiring either local file access or load-entire-network-modify-save semantics incompatible with PgApi's per-operation transaction model. -
IN
startup-performance-uses-lazy-loading
Both the API and CLI layers defer importing heavy modules (derive, compact, ask, asyncio, Storage) to function bodies rather than module top-level, minimizing import-time overhead for CLI responsiveness. -
IN
three-layer-architecture
The codebase is a three-layer stack: data model (`__init__.py`), TMS engine (`network.py`), and persistence (`storage.py`), with `api.py` providing functional API and `cli.py` as a thin argparse wrapper. -
IN
three-layer-stack-has-clean-boundaries
The architecture enforces strict layer separation: pure data model at bottom, context-managed API with dict returns in the middle, and pure-formatter CLI at the top -
IN
update-node-preserves-justifications
`api.update_node` modifies text and source metadata without altering the node's justification list or truth value. -
OUT
user-interface-is-verified-and-fault-tolerant
The complete user-facing stack is both structurally verified (pure delegation with hermetic integration tests ensuring no business logic leaks into the CLI) and operationally resilient (every information flow path is fault-tolerant with graceful degradation and governed output) — users never encounter unverified logic or ungoverned failure modes. -
OUT
verified-interface-controls-bidirectional-flow
All information flowing through the system's verified, fault-tolerant user interface is controlled in both directions: inbound through production-hardened LLM integration with bounded execution and fail-soft semantics, outbound through deterministic authorized access-controlled output — the verified interface serves as a trustworthy gateway that neither admits uncontrolled inputs nor emits unauthorized outputs.