Skip to content

feat(applications): add read-only maintainer context CLI - #229

Merged
stone16 merged 12 commits into
mainfrom
stone16/issue-216-maintainer-cli
Aug 3, 2026
Merged

feat(applications): add read-only maintainer context CLI#229
stone16 merged 12 commits into
mainfrom
stone16/issue-216-maintainer-cli

Conversation

@stone16

@stone16 stone16 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Closes #216.

Summary

  • Adds the review-renamable context-engine-context query|inspect read-only maintainer surface with human and strict JSON output.
  • Keeps Acquire input closed, generates fresh internal request IDs, validates untrusted file/stdin captures, renders complete Evidence/ACL citation lineage, and reports citation opening truthfully as NOT_ACTIVE.
  • Preserves environment-only bearer handling, loopback-only bounded transport, content-free stable exits, complete Package/digest/lifetime checks, and redacts any live egress-grant value before output or capture.
  • Shares only the data/transport seams needed by the existing dogfood consumers; the CLI import graph excludes Runtime, Control, Learning, persistence, Supply, ActionPlane, and BotDelivery.
  • Documents the operator contract and records ADR-0088's second-consumer revisit without adding a new process or authority boundary.

Maintainer decisions: v1 remains context-engine-context; inspection accepts explicitly untrusted file/stdin captures; no citation-open, evaluation, Control, ActionPlane, model, effect, or promotion commands are exposed; a valid saved unavailable-request capture returns exit 10; served content-free status classes map narrowly to stable exits, including authentication failure to invalid configuration while transport/5xx stays service unavailable.

Verification

  • make lint: passed.
  • make typecheck: passed on 511 Python sources and all TypeScript surfaces.
  • make test: 2,612 passed.
  • Exact pushed head: full GitHub checks passed, including catalog, process, real PostgreSQL integration/security, M0 security gate, and shipped-artifact governance; CodeRabbit passed.
  • Real installed CLI session against a locally served PostgreSQL composition passed: fresh query, strict JSON capture, human package inspection with citations, NOT_ACTIVE, and content-free tampered-capture refusal. The sanitized local transcript contains no bearer, redeemable egress grant, package identifiers, digests, or opaque lineage references.

The README edits are coordinator-required drive-by corrections removing known-false volatile line/ADR counts while retaining their semantic architecture claims.

No merge is requested by this automation; the PR remains for maintainer review.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a read-only maintainer Context CLI, a shared loopback dogfood HTTP client, centralized public package validation and digest logic, installation wiring, comprehensive tests, and updated capability and operational documentation.

Changes

Maintainer Context CLI

Layer / File(s) Summary
Shared public contract primitives
engine/public_contract.py, engine/runtime/..., adapters/http/contracts.py
Centralizes ContextPackage completion, projected-field validation, canonical JSON handling, SHA-256 digests, and public-document verification. Runtime and HTTP contracts reuse these functions.
Shared dogfood resolve transport
applications/dogfood_client.py, applications/dogfood_evaluation.py, applications/local_context.py
Adds bounded loopback HTTP configuration, secret exclusion, request validation, resolve/acquire calls, redirect and proxy rejection, response limits, and compatibility re-exports.
Maintainer CLI behavior and verification
applications/maintainer_context.py, pyproject.toml, tests/unit/..., tests/integration/..., tests/process/...
Adds query and inspect commands with human and JSON output, package validation, refusal handling, redaction, stable exit codes, and CLI integration coverage.
Capability and repository documentation
README.md, README.zh-CN.md, STATUS.md, docs/operations/..., docs/decisions/...
Documents CLI boundaries, loopback acquisition, package inspection, refusal behavior, inactive capabilities, repository structure, and the shared consumer seam.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The CLI implements the requested query and inspection commands, validation, output, safeguards, stable exits, documentation, and boundary controls for issue #216.
Out of Scope Changes check ✅ Passed The contract extraction, dogfood client, documentation, ADR update, and tests directly support the CLI objectives in issue #216.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a read-only maintainer context CLI.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch stone16/issue-216-maintainer-cli
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch stone16/issue-216-maintainer-cli

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12b84b7a37

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread applications/maintainer_context.py Outdated
Comment on lines +301 to +304
if cast(dict[str, object], grant).get("value") != REDACTED_EGRESS_GRANT:
return raw
envelope = dict(raw)
envelope["egressGrant"] = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the redacted grant before discarding it

stometa, when an untrusted capture contains egressGrant.value == "REDACTED-EGRESS-GRANT", this replaces the entire grant with None before Pydantic validation, so an object with an invalid kind or forbidden extra fields is accepted and its package is rendered. This violates the documented closed-envelope inspection contract; substitute a pattern-valid placeholder for only the value while preserving the original grant structure for validation.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 72015af7d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"",
f"block {ordinal}:",
f" blockId: {block.blockId}",
f" text: {block.text}",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Escape control characters before terminal rendering

stometa, when a queried document or untrusted capture contains ANSI/OSC control characters in an otherwise valid Block, the human renderer writes block.text directly to the maintainer's terminal. A recomputed package digest does not prevent this, so authorized or captured content can spoof CLI fields, alter terminal state, or attempt clipboard manipulation; escape or reject terminal control characters while preserving the original bytes in JSON output.

Useful? React with 👍 / 👎.

return _PackageCapture(
package=outcome.package,
document=package_document,
expires_at=_validated_expiry(outcome.package),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject naive ACL timestamps during capture validation

stometa, when an untrusted capture has a correctly recomputed digest but uses a naive sourceAclEvidence.aclAsOf—or the corresponding live/weak checkedAt, snapshotAsOf, or expiresAt—Pydantic accepts the datetime and this validation checks only the Package's outer asOf/expiresAt. The capture is therefore reported as valid and malformed ACL timing is rendered as citation lineage; validate every Evidence and SourceAclEvidence instant as timezone-aware and UTC-normalizable before accepting the capture.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (7)
engine/public_contract.py (2)

72-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move None to the end of both unions to clear RUF036.

Ruff reports RUF036 at lines 73-79 and 85. Reordering the union members changes no behavior.

♻️ Proposed reordering
 type CanonicalJsonValue = (
-    None
-    | bool
+    bool
     | int
     | float
     | str
     | list["CanonicalJsonValue"]
     | dict[str, "CanonicalJsonValue"]
+    | None
 )
 
 
 def _json_value(value: object, ancestors: set[int]) -> CanonicalJsonValue:
     if type(value) in (type(None), bool, float):
-        return cast(None | bool | float, value)
+        return cast(bool | float | None, value)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@engine/public_contract.py` around lines 72 - 85, Reorder the union members in
CanonicalJsonValue and the return annotation of _json_value so None appears last
in each union, preserving all existing members and behavior.

Source: Linters/SAST tools


50-63: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Document that continuation is omitted from packageDigest.

ContextPackageWire.continuation accepts a ContinuationOfferWire, but the public digest document always forces continuation: None before hashing. The current context-package-canonical-json-v3 profile is consistent across Runtime and HTTP, but it needs an explicit note that continuation offers are transmitted but not covered by packageDigest.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@engine/public_contract.py` around lines 50 - 63, Update the documentation for
the context-package-canonical-json-v3 profile near
complete_context_package_nullable_fields to explicitly state that
ContextPackageWire.continuation may be transmitted but is always normalized to
null and therefore omitted from packageDigest coverage; keep the existing
canonicalization behavior unchanged.
applications/maintainer_context.py (1)

379-380: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align ensure_ascii with the other JSON renderings.

Line 380 omits ensure_ascii=False. Lines 393 and 461 set it. A non-ASCII projected field name renders as an escape sequence here and as a literal elsewhere. Use the same setting for consistent human output.

♻️ Proposed consistency fix
                 "    projectedFields: "
-                + json.dumps(list(evidence.projectedFields), separators=(",", ":")),
+                + json.dumps(
+                    list(evidence.projectedFields),
+                    ensure_ascii=False,
+                    separators=(",", ":"),
+                ),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@applications/maintainer_context.py` around lines 379 - 380, Update the
projectedFields JSON rendering in the evidence formatting logic to pass
ensure_ascii=False to json.dumps, matching the settings used by the other JSON
renderings such as those near lines 393 and 461. Preserve the existing compact
separators and field serialization.
tests/integration/test_maintainer_context_cli.py (2)

156-168: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

This assertion cannot fail.

captured_envelope and envelope both come from parsing the same machine.stdout string. Line 168 compares two decodings of one source, so it proves nothing about the file round trip. Compare the file text against machine.stdout instead, or remove the assertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/test_maintainer_context_cli.py` around lines 156 - 168,
Update the assertion in the test around captured_envelope and envelope so it
validates the file round trip rather than comparing two objects parsed from the
same machine.stdout source. Compare the serialized capture file content with
machine.stdout, or remove the redundant assertion if that round-trip check is
covered elsewhere.

60-80: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Shutdown handlers never run, so the composed engines are not disposed.

Config(..., lifespan="off") disables the ASGI lifespan protocol. create_dogfood_app registers runtime_engine.dispose, control_engine.dispose, and roots.close as shutdown event handlers (adapters/http/dogfood.py lines 361-366). With lifespan off, none of those handlers run, so the database engines and file roots stay open for the rest of the session.

Enable the lifespan protocol, or dispose the engines explicitly in the finally block.

Also close the listener only after the thread stops. Line 78 closes the socket while the server thread may still own it.

♻️ Proposed cleanup ordering
     finally:
         server.should_exit = True
         thread.join(timeout=5)
-        listener.close()
         if thread.is_alive():
             raise RuntimeError("local CLI session server did not stop")
+        listener.close()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/test_maintainer_context_cli.py` around lines 60 - 80,
Update the local CLI session server setup around Config and the try/finally
cleanup: enable ASGI lifespan so create_dogfood_app shutdown handlers dispose
runtime_engine, control_engine, and roots, then signal shutdown and join the
server thread before closing listener. Preserve the existing startup failure
cleanup and raise if the thread remains alive.
tests/unit/test_maintainer_context_cli.py (1)

522-536: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Bound every CLI subprocess invocation added by this PR. Both sites launch the maintainer CLI without a timeout, while the sibling helpers _command and _run_cli set one. A hang at either site blocks the suite with no failure signal.

  • tests/unit/test_maintainer_context_cli.py#L522-L536: add timeout=30 to the subprocess.run call that lists imported modules.
  • tests/process/test_processes.py#L77-L83: add timeout=30 to the subprocess.run call that runs context-engine-context --help.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_maintainer_context_cli.py` around lines 522 - 536, Bound both
added CLI subprocess invocations with a 30-second timeout: update the
subprocess.run call in tests/unit/test_maintainer_context_cli.py at lines
522-536 and the subprocess.run call in tests/process/test_processes.py at lines
77-83. No other changes are needed.
tests/process/test_processes.py (1)

77-83: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Add a timeout to this subprocess call.

The other CLI subprocess tests in this PR bound their invocations. This call has none. If the console script fails to resolve or hangs, the test blocks with no failure signal.

♻️ Proposed bound
         cwd=ROOT,
         check=False,
         capture_output=True,
         text=True,
+        timeout=30,
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/process/test_processes.py` around lines 77 - 83, Add a finite timeout
to the subprocess.run invocation in the context-engine-context help test,
matching the bounded CLI subprocess tests nearby. Keep the existing command and
result handling unchanged while ensuring hangs or unresolved console scripts
terminate with a failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@docs/decisions/0088-bind-local-consumers-to-fresh-evidence-bearing-packages.md`:
- Around line 123-129: Correct the temporal wording in the ADR paragraph
beginning “On 2026-08-03”: use the verified date when issue `#216` actually fired,
or rewrite the statement as planned/future wording if it has not occurred yet.
Preserve the surrounding claims about applications/dogfood_client.py, the
obligations, category boundary, and NOT_ACTIVE carriers.

In `@README.md`:
- Around line 534-536: Remove the numeric test-to-engine ratio from README.md
lines 534-536 and README.zh-CN.md lines 246-247, replacing each with a
qualitative non-numeric statement while preserving the surrounding meaning.
- Line 513: Clarify the applications/ boundary in all four documented locations:
in README.md lines 513 and 529-533, state that applications may handle CLI
validation, rendering, and loopback HTTP transport while authorization,
retrieval decisions, and delivery remain in engine/; make the equivalent updates
in README.zh-CN.md lines 226 and 242-245, preserving the existing
repository-layout and architecture descriptions.

In `@tests/unit/test_maintainer_context_cli.py`:
- Around line 304-313: Update the expired-package fixture in
test_expired_package_is_content_free_and_has_stable_exit_class to use an
unambiguously historical expiresAt value far before the current date, while
keeping the related timestamps and packageDigest recalculation consistent.
Ensure the fixture deterministically remains expired regardless of the test
execution time.

---

Nitpick comments:
In `@applications/maintainer_context.py`:
- Around line 379-380: Update the projectedFields JSON rendering in the evidence
formatting logic to pass ensure_ascii=False to json.dumps, matching the settings
used by the other JSON renderings such as those near lines 393 and 461. Preserve
the existing compact separators and field serialization.

In `@engine/public_contract.py`:
- Around line 72-85: Reorder the union members in CanonicalJsonValue and the
return annotation of _json_value so None appears last in each union, preserving
all existing members and behavior.
- Around line 50-63: Update the documentation for the
context-package-canonical-json-v3 profile near
complete_context_package_nullable_fields to explicitly state that
ContextPackageWire.continuation may be transmitted but is always normalized to
null and therefore omitted from packageDigest coverage; keep the existing
canonicalization behavior unchanged.

In `@tests/integration/test_maintainer_context_cli.py`:
- Around line 156-168: Update the assertion in the test around captured_envelope
and envelope so it validates the file round trip rather than comparing two
objects parsed from the same machine.stdout source. Compare the serialized
capture file content with machine.stdout, or remove the redundant assertion if
that round-trip check is covered elsewhere.
- Around line 60-80: Update the local CLI session server setup around Config and
the try/finally cleanup: enable ASGI lifespan so create_dogfood_app shutdown
handlers dispose runtime_engine, control_engine, and roots, then signal shutdown
and join the server thread before closing listener. Preserve the existing
startup failure cleanup and raise if the thread remains alive.

In `@tests/process/test_processes.py`:
- Around line 77-83: Add a finite timeout to the subprocess.run invocation in
the context-engine-context help test, matching the bounded CLI subprocess tests
nearby. Keep the existing command and result handling unchanged while ensuring
hangs or unresolved console scripts terminate with a failure.

In `@tests/unit/test_maintainer_context_cli.py`:
- Around line 522-536: Bound both added CLI subprocess invocations with a
30-second timeout: update the subprocess.run call in
tests/unit/test_maintainer_context_cli.py at lines 522-536 and the
subprocess.run call in tests/process/test_processes.py at lines 77-83. No other
changes are needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e128167e-a214-45da-9be6-f836c2ccd3a8

📥 Commits

Reviewing files that changed from the base of the PR and between 53b784b and 72015af.

📒 Files selected for processing (18)
  • README.md
  • README.zh-CN.md
  • STATUS.md
  • adapters/http/contracts.py
  • applications/dogfood_client.py
  • applications/dogfood_evaluation.py
  • applications/local_context.py
  • applications/maintainer_context.py
  • docs/decisions/0088-bind-local-consumers-to-fresh-evidence-bearing-packages.md
  • docs/operations/maintainer-context-cli.md
  • engine/public_contract.py
  • engine/runtime/contracts.py
  • engine/runtime/evidence.py
  • engine/runtime/package_digest.py
  • pyproject.toml
  • tests/integration/test_maintainer_context_cli.py
  • tests/process/test_processes.py
  • tests/unit/test_maintainer_context_cli.py

Comment on lines +123 to +129

On 2026-08-03, issue #216 fired the first clause: the read-only maintainer
Context CLI is the second kind of local consumer, and
`applications/dogfood_client.py` is now the shared data and transport seam that
both it and the Claude Code skill call. The four obligations, the local
read-only category boundary, and every carrier this decision leaves
`NOT_ACTIVE` are unchanged, so no superseding decision is required.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the actual issue date.

The ADR says “On 2026-08-03” in past tense, but the current date is August 2, 2026. Replace it with the date when issue #216 actually fired, or use planned wording until that date occurs.

🧰 Tools
🪛 LanguageTool

[style] ~125-~125: Phrases like “kind of” can make your writing sound less confident. You could consider removing it to sound more confident.
Context: ...ly maintainer Context CLI is the second kind of local consumer, and `applications/dogfo...

(KIND_OF_SORT_OF_2)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/decisions/0088-bind-local-consumers-to-fresh-evidence-bearing-packages.md`
around lines 123 - 129, Correct the temporal wording in the ADR paragraph
beginning “On 2026-08-03”: use the verified date when issue `#216` actually fired,
or rewrite the statement as planned/future wording if it has not occurred yet.
Preserve the surrounding claims about applications/dogfood_client.py, the
obligations, category boundary, and NOT_ACTIVE carriers.

Comment thread README.md
Comment thread README.md
Comment on lines +534 to +536
- **Tests outweigh implementation ~3:1.** The `tests/` tree is several times the
size of `engine/`. For a project whose central claim is a security invariant,
the executable evidence *is* the product.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the numeric repository-size claim from both READMEs.

Both changed sections retain an approximate 3:1 test-to-engine ratio, although the PR removes volatile README counts. Use a non-numeric qualitative statement in both languages.

  • README.md#L534-L536: remove ~3:1.
  • README.zh-CN.md#L246-L247: remove “约为实现量的 3 倍”.
📍 Affects 2 files
  • README.md#L534-L536 (this comment)
  • README.zh-CN.md#L246-L247
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 534 - 536, Remove the numeric test-to-engine ratio
from README.md lines 534-536 and README.zh-CN.md lines 246-247, replacing each
with a qualitative non-numeric statement while preserving the surrounding
meaning.

Comment on lines +304 to +313
def test_expired_package_is_content_free_and_has_stable_exit_class() -> None:
package = _package_document()
package["asOf"] = "2026-08-02T12:00:00Z"
package["expiresAt"] = "2026-08-02T12:05:00Z"
evidence = cast(list[dict[str, object]], package["evidence"])[0]
evidence["authorizationAsOf"] = "2026-08-02T12:00:00Z"
source_acl = cast(dict[str, object], evidence["sourceAclEvidence"])
source_acl["aclAsOf"] = "2026-08-02T12:00:00Z"
package.pop("packageDigest")
package["packageDigest"] = context_package_digest(package)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use an unambiguously historical instant for the expired-package fixture.

The fixture sets expiresAt to 2026-08-02T12:05:00Z. That instant is not clearly in the past. The CLI compares it against datetime.now(tz=UTC), so the assertion returncode == 13 depends on the wall clock at the moment the suite runs. Use a far-past instant to make the expiry deterministic, in the same way the current-package fixture uses 2099.

🐛 Proposed deterministic fixture
-    package["asOf"] = "2026-08-02T12:00:00Z"
-    package["expiresAt"] = "2026-08-02T12:05:00Z"
+    package["asOf"] = "2000-01-01T12:00:00Z"
+    package["expiresAt"] = "2000-01-01T12:05:00Z"
     evidence = cast(list[dict[str, object]], package["evidence"])[0]
-    evidence["authorizationAsOf"] = "2026-08-02T12:00:00Z"
+    evidence["authorizationAsOf"] = "2000-01-01T12:00:00Z"
     source_acl = cast(dict[str, object], evidence["sourceAclEvidence"])
-    source_acl["aclAsOf"] = "2026-08-02T12:00:00Z"
+    source_acl["aclAsOf"] = "2000-01-01T12:00:00Z"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_expired_package_is_content_free_and_has_stable_exit_class() -> None:
package = _package_document()
package["asOf"] = "2026-08-02T12:00:00Z"
package["expiresAt"] = "2026-08-02T12:05:00Z"
evidence = cast(list[dict[str, object]], package["evidence"])[0]
evidence["authorizationAsOf"] = "2026-08-02T12:00:00Z"
source_acl = cast(dict[str, object], evidence["sourceAclEvidence"])
source_acl["aclAsOf"] = "2026-08-02T12:00:00Z"
package.pop("packageDigest")
package["packageDigest"] = context_package_digest(package)
def test_expired_package_is_content_free_and_has_stable_exit_class() -> None:
package = _package_document()
package["asOf"] = "2000-01-01T12:00:00Z"
package["expiresAt"] = "2000-01-01T12:05:00Z"
evidence = cast(list[dict[str, object]], package["evidence"])[0]
evidence["authorizationAsOf"] = "2000-01-01T12:00:00Z"
source_acl = cast(dict[str, object], evidence["sourceAclEvidence"])
source_acl["aclAsOf"] = "2000-01-01T12:00:00Z"
package.pop("packageDigest")
package["packageDigest"] = context_package_digest(package)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_maintainer_context_cli.py` around lines 304 - 313, Update the
expired-package fixture in
test_expired_package_is_content_free_and_has_stable_exit_class to use an
unambiguously historical expiresAt value far before the current date, while
keeping the related timestamps and packageDigest recalculation consistent.
Ensure the fixture deterministically remains expired regardless of the test
execution time.

@stone16
stone16 force-pushed the stone16/issue-216-maintainer-cli branch from 72015af to a0c5580 Compare August 3, 2026 00:03
@stone16 stone16 changed the title feat: add read-only maintainer context CLI feat(applications): add read-only maintainer context CLI Aug 3, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0c55802dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +70 to +73
query.add_argument("--max-tokens", type=int)
query.add_argument("--max-provider-calls", type=int)
query.add_argument("--max-cost-microunits", type=int)
query.add_argument("--max-elapsed-ms", type=int)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Map argparse failures to the documented exit class

stometa, when a budget value is syntactically invalid, such as --max-tokens not-an-int, argparse exits here with status 2 before _query can classify it. This breaks the CLI's documented stable exit contract, which assigns invalid budget and request inputs to exit 14, so automation cannot reliably distinguish these inputs from an undocumented failure; route parser errors through EXIT_INVALID_CONFIGURATION instead.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
tests/integration/test_maintainer_context_cli.py (2)

87-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not raise from the finally block on the teardown path.

If the with body fails, the finally block still runs. If the uvicorn thread is then still alive, line 94 raises RuntimeError. That new exception replaces the original assertion failure, so the real cause of the test failure is lost. Report the stuck thread as a warning instead, or check liveness only when no exception is in flight.

♻️ Proposed teardown that preserves the original failure
     try:
         yield
     finally:
         server.should_exit = True
         thread.join(timeout=5)
         listener.close()
-        if thread.is_alive():
-            raise RuntimeError("local CLI session server did not stop")
+        stuck = thread.is_alive()
+    if stuck:
+        raise RuntimeError("local CLI session server did not stop")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/test_maintainer_context_cli.py` around lines 87 - 94,
Update the teardown around the server thread in the test’s context manager so a
still-alive thread is reported as a warning rather than raised from the finally
block. Preserve the original exception from the with body while retaining the
existing shutdown, join, and listener cleanup behavior.

51-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Give a clear failure when the console script is not installed.

_run_cli starts context-engine-context by bare name. The test then depends on the entry point being present on PATH. If the package is not installed in the active environment, subprocess.run raises FileNotFoundError and the report shows no reason. Resolve the executable first and skip with an explicit message.

♻️ Proposed guard
+import shutil
+
 def _run_cli(*arguments: str) -> subprocess.CompletedProcess[str]:
+    executable = shutil.which("context-engine-context")
+    if executable is None:
+        pytest.skip("context-engine-context console script is not installed")
     return subprocess.run(
-        ["context-engine-context", *arguments],
+        [executable, *arguments],
         check=False,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/test_maintainer_context_cli.py` around lines 51 - 59,
Update _run_cli to resolve the context-engine-context executable before invoking
subprocess.run, and catch FileNotFoundError when it is unavailable. Skip the
test with an explicit message explaining that the console script is not
installed, while preserving the existing subprocess arguments and timeout.
applications/dogfood_evaluation.py (1)

13-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Promote the shared helpers to public names instead of importing private ones.

This module imports _as_object, _package_from_outcome, _require_exact_text, and _require_opaque_ref from applications.dogfood_client. The leading underscore marks them as module-private, so this import crosses a declared module boundary. dogfood_client already exposes validate_dogfood_query and validate_dogfood_request_id for two of these uses. Give the remaining helpers public names in dogfood_client, then import only public symbols here.

Run this script to list the call sites that must change:

#!/bin/bash
rg -nP --type=py '\b(_as_object|_package_from_outcome|_require_exact_text|_require_opaque_ref)\b' -C2

As per coding guidelines: "engine, adapters, bot_delivery, action_plane, and supporting modules must respect their declared process and module boundaries."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@applications/dogfood_evaluation.py` around lines 13 - 26, Promote the shared
helpers currently named _as_object, _package_from_outcome, _require_exact_text,
and _require_opaque_ref in dogfood_client to public names, update all call sites
to use those names, and change this import to include only the public symbols.
Preserve the existing helper behavior and use the repository-wide call-site
search to ensure no private references remain.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@applications/dogfood_evaluation.py`:
- Around line 13-26: Promote the shared helpers currently named _as_object,
_package_from_outcome, _require_exact_text, and _require_opaque_ref in
dogfood_client to public names, update all call sites to use those names, and
change this import to include only the public symbols. Preserve the existing
helper behavior and use the repository-wide call-site search to ensure no
private references remain.

In `@tests/integration/test_maintainer_context_cli.py`:
- Around line 87-94: Update the teardown around the server thread in the test’s
context manager so a still-alive thread is reported as a warning rather than
raised from the finally block. Preserve the original exception from the with
body while retaining the existing shutdown, join, and listener cleanup behavior.
- Around line 51-59: Update _run_cli to resolve the context-engine-context
executable before invoking subprocess.run, and catch FileNotFoundError when it
is unavailable. Skip the test with an explicit message explaining that the
console script is not installed, while preserving the existing subprocess
arguments and timeout.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 668d0346-9a62-4941-871f-6d9bfd842c03

📥 Commits

Reviewing files that changed from the base of the PR and between 72015af and a0c5580.

📒 Files selected for processing (18)
  • README.md
  • README.zh-CN.md
  • STATUS.md
  • adapters/http/contracts.py
  • applications/dogfood_client.py
  • applications/dogfood_evaluation.py
  • applications/local_context.py
  • applications/maintainer_context.py
  • docs/decisions/0088-bind-local-consumers-to-fresh-evidence-bearing-packages.md
  • docs/operations/maintainer-context-cli.md
  • engine/public_contract.py
  • engine/runtime/contracts.py
  • engine/runtime/evidence.py
  • engine/runtime/package_digest.py
  • pyproject.toml
  • tests/integration/test_maintainer_context_cli.py
  • tests/process/test_processes.py
  • tests/unit/test_maintainer_context_cli.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • applications/local_context.py
  • pyproject.toml
  • STATUS.md
  • engine/runtime/evidence.py
  • engine/runtime/contracts.py
  • adapters/http/contracts.py
  • engine/runtime/package_digest.py
  • README.zh-CN.md

stone16 added 12 commits August 3, 2026 08:15
…ainer CLI test

CI builds the pull request's merge commit, not this branch tip. On main,
`create_dogfood_app` now constructs a real `LocalQwenEmbeddingProvider`, and
`test_dogfood_runtime_activation` keeps that network-free by patching the
provider in a module-local autouse fixture while its `_environment` helper
still hands out the placeholder model directory.

The maintainer CLI test reuses those helpers, but pytest applies an autouse
fixture only to the module that defines it, so the merged composition loaded
the placeholder directory for real and refused with
`DogfoodConfigurationUnavailable`.

Import the twin fixture beside the helpers it belongs to, which states the
dependency the module actually has instead of inheriting it by file position.
… dedupe dogfood validators, tighten capture grant guard
@stone16
stone16 force-pushed the stone16/issue-216-maintainer-cli branch from a0c5580 to 5b511e8 Compare August 3, 2026 00:28

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b511e8b9f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

raw = capture.read(MAX_CAPTURE_BYTES + 1)
if len(raw) > MAX_CAPTURE_BYTES:
raise ValueError("capture is too large")
return json.loads(raw)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject duplicate keys in untrusted JSON captures

stometa, when an untrusted capture repeats a security-relevant member such as kind, package, or packageDigest, json.loads silently keeps only the last occurrence, so the CLI can report an ambiguous, non-closed document as valid even though another JSON consumer may interpret a different value. Use an object_pairs_hook (or equivalent strict decoder) that rejects duplicate object names before schema and digest validation.

Useful? React with 👍 / 👎.

category=capture.category,
)
return EXIT_EXPLICIT_REFUSAL
if datetime.now(tz=UTC) >= capture.expires_at:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recheck expiry after preparing output

stometa, when an inspected capture is only slightly short of expiresAt—especially a large capture rendered in human form—this check can pass before serialization/rendering and the Package content can then be printed after expiry. Buffer the selected output first and perform the final expiry check immediately before emitting it so near-expiry captures fail closed rather than presenting expired context.

Useful? React with 👍 / 👎.

@stone16
stone16 merged commit 746897f into main Aug 3, 2026
2 checks passed
@stone16
stone16 deleted the stone16/issue-216-maintainer-cli branch August 3, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[W2][CLI] Build a first-class maintainer CLI for resolve and ContextPackage inspection

1 participant