feat(cli): hcs policy status + ADR 0080 (D-087) - #100
Merged
Conversation
The first invocable surface this project has produced.
$ just cli policy status
snapshot .../policies/generated-snapshot/tiers.yaml
status loaded
digest sha256:7e30b768... (observed, not verified — see ADR 0079)
schema policy_rule_schema_version 0.1.0
rules 8
OPERATION CLASS TIER APPROVAL PATH CEILING
read_only_diagnostic read-safe false false not_applicable
worktree_mutation write-project true true PT24H
destructive_git write-destructive true true PT1H
...
THE CITATION RULE WORKED PROSPECTIVELY
No ADR authorized a CLI surface. ADR 0003 covers stdio + Streamable HTTP
transports. ADR 0079 §Out of scope excludes "any consumer of the returned
rules" — which a verb rendering those rules is.
Citing either would have been trap #61's third occurrence. Checking first
caught it before a false citation was written, which is the first time that
rule has prevented rather than diagnosed. ADR 0080 ships here with the code.
ADR 0079's PRECONDITION, DISCHARGED
ADR 0079 §Out of scope: "before an adapter forwards a path argument, the public
form must become kernel-resolved." So `@hcs/kernel/api` now exports
`loadBoundPolicyRules()`, which takes no path and cannot be pointed at an
arbitrary file. `resolveBoundSnapshotPath()` reads HCS_ROOT and falls back to
the kernel module's own location — charter inv. 15 warns GUI apps, launchd
jobs, and IDE extensions do not inherit shell env, so a resolver that only read
the variable would fail in exactly those contexts. The parameterized form stays
for tests, which must stage mutated snapshots.
TWO GATE BUGS, FOUND BY HAVING A SUBJECT
boundary-check rule 2 is `[ -d packages/adapters ]`-guarded and scans adapters
only. Until this package existed it had no subject. Its first real one exposed
two false positives in the repair that landed in #93:
1. The subtraction required `/api/` with a trailing slash, so it matched
`@hcs/kernel/api/policy` but NOT `@hcs/kernel/api` — the exact path
charter:84 declares legal. The rule rejected the correct import.
2. Stage 1 matched any occurrence of the token, so it then flagged the
adapter's own documentation, where the path appears in backticks.
Fixed by anchoring the subtraction on quote/slash/end and requiring stage 1 to
match a QUOTED specifier — every import form quotes it; prose does not.
Negative-controlled both ways: a planted `@hcs/kernel/src/policy/...` import and
a planted `../../../kernel/src/policy/...` traversal both still go red.
Separately, the justfile's scoped-test discovery globbed `packages/*/tests` and
missed nested packages, so `just test cli` could not find
`packages/adapters/cli/tests`. Now covers `packages/*/*/tests` too; the
unknown-target error lists `kernel`, `schemas`, `cli`.
READ-ONLY BY CONSTRUCTION
Registers no capability, emits no OperationShape, mints and consumes no
ApprovalGrant, spawns nothing, writes nothing. Dispatch is an exhaustive match
over a closed verb list, not a lookup with a default handler: unknown verb exits
2, rejected snapshot exits 1. `run(argv)` returns {exitCode, lines} so the verb
is testable without spawning a process or capturing streams.
The adapter decides nothing (inv. 1). It formats the loader's result and does
nothing else. A guard derived from policyRuleTierSchema.options asserts no tier
literal appears in adapter source, and a second asserts every @hcs/kernel import
is exactly `@hcs/kernel/api`.
The digest is labelled "observed, not verified" in the output, because ADR 0079
cut provenance verification from the loader's scope and the verb must not imply
a guarantee the kernel does not make. A test asserts that label is present.
The kernel surface lock fired again when the barrel widened — third time it has
caught an unannounced API change.
Class E — adapter read path.
Validation: `just verify` green. `just test cli` 8/8, `just test kernel` 29/29,
full suite 556.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The first invocable surface this project has produced.
Change class: E — adapter read path. Ledger row D-087. ADR 0080 ships in this PR.
The citation rule worked prospectively
No ADR authorized a CLI surface.
adr/0003covers stdio + Streamable HTTP transports.adr/0079§Out of scope excludes "any consumer of the returned rules" — which a verb rendering those rules is.Citing either would have been trap #61's third occurrence. Checking first caught it before a false citation was written — the first time that rule has prevented rather than diagnosed.
ADR 0079's precondition, discharged
@hcs/kernel/apinow exportsloadBoundPolicyRules(), which takes no path and cannot be pointed at an arbitrary file.resolveBoundSnapshotPath()readsHCS_ROOTand falls back to the kernel module's own location — charter inv. 15 warns GUI apps, launchd jobs and IDE extensions do not inherit shell env, so a resolver that only read the variable would fail in exactly those contexts. The parameterized form stays for tests, which must stage mutated snapshots.Two gate bugs, found by finally having a subject
boundary-checkrule 2 is[ -d packages/adapters ]-guarded and scans adapters only. Until this package existed it had no subject. Its first real one exposed two false positives in the repair that landed in #93:/api/with a trailing slash — matching@hcs/kernel/api/policybut not@hcs/kernel/api, the exact pathcharter:84declares legal. The rule rejected the correct import.Fixed by anchoring the subtraction on quote/slash/end, and requiring stage 1 to match a quoted specifier — every import form quotes it; prose does not. Negative-controlled both ways:
Separately, the justfile's scoped-test discovery globbed
packages/*/testsand missed nested packages, sojust test clicouldn't findpackages/adapters/cli/tests. Now coverspackages/*/*/tests; the unknown-target error listskernel,schemas,cli.Read-only by construction
Registers no
Capability, emits noOperationShape, mints and consumes noApprovalGrant, spawns nothing, writes nothing.Dispatch is an exhaustive match over a closed verb list, not a lookup with a default handler — unknown verb exits
2, rejected snapshot exits1. A test assertspolicyalone is not treated as a prefix ofpolicy status.run(argv)returns{exitCode, lines}, so the verb is testable without spawning a process or capturing streams.The adapter decides nothing (inv. 1). It formats the loader's result. A guard derived from
policyRuleTierSchema.optionsasserts no tier literal appears in adapter source; a second asserts every@hcs/kernelimport is exactly@hcs/kernel/api.The digest is labelled "observed, not verified" in the output, because ADR 0079 cut provenance verification from the loader's scope and the verb must not imply a guarantee the kernel does not make. A test asserts that label is present.
The kernel surface lock fired again when the barrel widened — third time it has caught an unannounced API change.
Boundary checks
OperationShaperemains upstream ofCommandShape— none emittedCapability; mints and consumes noApprovalGrant@hcs/kernel/api— enforced by the exports map and by testadr/0079andadr/0003are cited only for what they exclude;adr/0079§Out of scope is quoted verbatim.Validation
just verifygreen.just test cli8/8,just test kernel29/29, full suite 556.