From e33f9c3ca55bdfb03d41ae796cae7aac29a3afe2 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 27 Sep 2026 16:15:01 +0200 Subject: [PATCH] Finish companion catalog work and reconcile completed approval tasks Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e324-abce-7e51-bb2b-496f097afdb0 --- catalog/glas-claude-agent-dev-anthropic.yaml | 50 ++++++++-- docs/2026-09-27-loose-end-closeout.md | 52 +++++++++++ docs/approval-consumption.md | 6 ++ docs/drafts/glas-exec-owner-configured.yaml | 14 +-- ...026-09-27-companion-catalog-readiness.json | 42 +++++++++ docs/exec-owner-binding.md | 5 +- docs/glas-claude-delivery.md | 18 ++-- docs/railiance-clock-consumer-review.md | 83 +++++++++++++++++ intakes/intakes.md | 11 ++- scripts/check_layer_conformance.py | 92 +++++++++---------- tests/test_exec_owner.py | 30 +++++- tests/test_human_control.py | 5 +- tests/test_layer_conformance.py | 23 ++++- .../SECRETS-WP-0006-catalog-lane-adoption.md | 14 ++- ...-WP-0007-production-lifecycle-hardening.md | 31 ++++++- ...-0008-layer-model-lifecycle-conformance.md | 32 ++++++- ...ETS-WP-0009-glas-claude-native-delivery.md | 18 +++- ...-WP-0011-multi-lane-exec-owner-delivery.md | 33 ++++++- 18 files changed, 472 insertions(+), 87 deletions(-) create mode 100644 docs/2026-09-27-loose-end-closeout.md create mode 100644 docs/evidence/2026-09-27-companion-catalog-readiness.json create mode 100644 docs/railiance-clock-consumer-review.md diff --git a/catalog/glas-claude-agent-dev-anthropic.yaml b/catalog/glas-claude-agent-dev-anthropic.yaml index 785d1ff..42e454e 100644 --- a/catalog/glas-claude-agent-dev-anthropic.yaml +++ b/catalog/glas-claude-agent-dev-anthropic.yaml @@ -3,8 +3,8 @@ kind: kv org: coulomb repo: sand-boxer stage: prod -description: Proposed native exec-env delivery for CCR-2026-0016. KV custody exists; - no runtime grant or activation yet. +description: Catalog-bound metered owner with Activity Core worker-token companion. + KV custody exists; native runtime approval and activation remain SECRETS-WP-0009-T03. mount: platform path: workloads/glas-harness/claude-agent-dev mount_management: existing @@ -19,12 +19,46 @@ consumers: workload_delivery: [] delivery_config: exec_owner: - status: pending - owner: rein-aharness MessagesOwner with sand-boxer runtime boundary - reason: Exact installed metered-once runtime is proved; native holder review, - immutable owner configuration and private state/profile/service admission - remain SECRETS-WP-0009-T03 and HFACT-WP-0001-T03/T04. No arbitrary child may - receive this key while the owner binding is pending. + status: configured + owner: rein-aharness MessagesOwner (metered-once) with sand-boxer runtime boundary + command: + - /home/tegwick/.helixforge-factory/runtimes/b6e4e8a429393d68831c996a65c0489664205df969ac9882e581983ec2da4969/bin/python3 + - -I + - -B + - -m + - rein_aharness.cli + - metered-once + - --owner-config + - /home/tegwick/hfact/owner-metered/owner.json + cwd: /home/tegwick/hfact/owner-metered + environment: + PATH: /usr/bin:/bin + LANG: C.UTF-8 + HOME: /home/tegwick + ACTIVITY_CORE_URL: http://127.0.0.1:8010 + AGENT_HARNESS_WORKER_ID: rein-aharness-metered@railiance01 + AGENT_HARNESS_OPS_LABELS: hfact-metered + AGENT_HARNESS_OPS_LABELS_MODE: all + AGENT_HARNESS_EXECUTION_PROJECT: prj-helixforge-factory + AGENT_HARNESS_REQUIRE_SPEND_ADMISSION: '1' + AGENT_HARNESS_REQUIRE_REQUEST_ADMISSION: '1' + AGENT_HARNESS_SPEND_POLICY: /home/tegwick/hfact/owner-metered/spend-policy.json + AGENT_HARNESS_SPEND_LEDGER: /home/tegwick/hfact/owner-metered/spend.sqlite3 + AGENT_HARNESS_REPO_MAP: '{"hfact-glas-proof":"/home/tegwick/hfact/targets/hfact-glas-proof"}' + files: + /home/tegwick/.helixforge-factory/runtimes/b6e4e8a429393d68831c996a65c0489664205df969ac9882e581983ec2da4969/bin/python3: + sha256: e50d468e8b0adfb05733f5b87b3cff34829c4a8c1aea50c865aa8bdfe4bb150f + private: false + /home/tegwick/hfact/owner-metered/owner.json: + sha256: 0e263f8299a42f63caf3595ff3eb7adc10354673f7b5125e0811def3bea7c274 + private: true + /home/tegwick/hfact/owner-metered/spend-policy.json: + sha256: f31c585916de1ea8bd8e48c72421803dfde1015e6201704b9320f77d2c545d9c + private: true + companions: + - catalog: activity-core-metered-worker-token + field: token + env: ACTIVITY_CORE_WORKER_TOKEN delivery_modes: - exec-env - read-check diff --git a/docs/2026-09-27-loose-end-closeout.md b/docs/2026-09-27-loose-end-closeout.md new file mode 100644 index 0000000..01fc0d7 --- /dev/null +++ b/docs/2026-09-27-loose-end-closeout.md @@ -0,0 +1,52 @@ +# Existing-work closeout — 2026-09-27 + +No new task or workplan was opened. Three tasks and one intake can close from +existing evidence plus the local completion below. One workplan finishes. + +| Existing record | Result | Basis | +| --- | --- | --- | +| SECRETS-WP-0007-T04 | done | Shared exact-action claim/PDP/consume implementation, regression refusals and real separate apply/verify/exec consumes in the September 16 native receipt | +| SECRETS-WP-0008-T02 | done | The same native receipt resolves its outstanding served-decision/consume dependency; current regression coverage retains fail-closed replay and lifetime checks | +| SECRETS-WP-0011-T04 | done | Activity Core custody/identity handoff received; configured owner and companion now cataloged; current owner/path/hash checks passed on railiance01 without backend access | +| SECRETS-WP-0011 | finished | All five tasks complete; native Glas activation remains in the existing SECRETS-WP-0009-T03 | +| SECRETS-IN-0003 | closed | Published signing/custody/bootstrap/rotation/revocation and bounded-time consumer review, with limits and existing owner acceptance work named | + +Implementation includes the private spend-policy pin and the estate reference +layer-version detector under SECRETS-WP-0008. The latter closes the stale +conformance-record question without inventing a durable-record requirement. + +Evidence: + +- [Native approval and delivery receipt](evidence/2026-09-16-t03-completion.json) + is historical acceptance for one exact OpenRouter key-check recipient. Its + consumed approvals grant no future action. +- [Companion catalog receipt](evidence/2026-09-27-companion-catalog-readiness.json) + records current backend-free owner validation, installed file pins and owner + digest. Configuration is not production approval or delivery readiness. +- [Clock consumer review](railiance-clock-consumer-review.md) closes a review + request, without enabling a new trust binding or claiming operational rotation. + +## Work that must remain open + +| Existing record | Remaining completion requirement | +| --- | --- | +| SECRETS-WP-0006-T05 | Approved native verification for the other catalog lanes; the OpenRouter key-check receipt covers one exact recipient only | +| SECRETS-WP-0006-T06 | Owner-agreed routing/proxy retirement per verified lane, plus custody disposition of the legacy npm pointer; no unilateral proxy retirement | +| SECRETS-WP-0007-T07 | Its acceptance includes native cutover and routing/proxy retirement under 0006-T05/T06; engine hardening alone does not satisfy it | +| SECRETS-WP-0008-T06 | Platform/KeyCape exact service claims and tenant, custody, provisioned scoped JWT role and native login/negative/revocation acceptance (RPF-WP-0035-T02); the recorded env-auth run does not prove this | +| SECRETS-WP-0009-T03 | Fresh exact per-action/per-lane approvals, unrelated negative identity, attended apply/verify, bounded real Glas delivery and revocation; recheck pins and spend validity in that window | +| SECRETS-IN-0002 | Confirmed flex-auth repository rename before changing checkout coordinates; FLEX-WP-0020 still holds the live rename | + +Workplans 0006, 0007, 0008 and 0009 therefore remain unfinished. Their remaining +requirements stay in those records, with no replacement or successor task. + + +Scope reconciliation is recorded as State Hub decision +`7a756027-2041-4732-bcbc-3bb6a5380838`; it grants no credential action. + + +Validation: 487 repository tests passed, including disposable OpenBao integration. +The layer-conformance checker and `git diff --check` passed. The configured +recipient's local-only owner check and exact engine path/pin checks passed on +railiance01; no backend credential was requested, no queue row was claimed and +no provider request was made. diff --git a/docs/approval-consumption.md b/docs/approval-consumption.md index de6b7e7..ea8fda8 100644 --- a/docs/approval-consumption.md +++ b/docs/approval-consumption.md @@ -218,3 +218,9 @@ and after CAS consume. The client keeps a boot-bound trust admission and private rollback floor. It never changes the OS clock, accepts a sample as its own trust bootstrap, or falls back to a shifted local timestamp. The option stays unset until the owner publishes trust through the admitted Railiance Clock deployment. + + +The [2026-09-27 consumer review](railiance-clock-consumer-review.md) records +signing compatibility, custody, bootstrap, rotation/revocation and check-to-use +limits. It reconciles SECRETS-IN-0003 with the already recorded September 16 +native use; it does not enable a new trust binding. diff --git a/docs/drafts/glas-exec-owner-configured.yaml b/docs/drafts/glas-exec-owner-configured.yaml index 5ea4e55..ac102bd 100644 --- a/docs/drafts/glas-exec-owner-configured.yaml +++ b/docs/drafts/glas-exec-owner-configured.yaml @@ -1,7 +1,6 @@ -# Draft exec_owner for glas-claude-agent-dev-anthropic (SECRETS-WP-0009-T03). -# Not in the catalog. Activity Core reports custody and identity live as of -# 2026-09-24 (ACTIVITY-WP-0039). Admission still requires current owner/pin -# validation and exact per-lane approvals for attended native activation. +# Configured binding promoted to catalog/glas-claude-agent-dev-anthropic.yaml +# on 2026-09-27 after backend-free owner and path/pin validation. +# Configuration is not runtime approval; native activation is SECRETS-WP-0009-T03. exec_owner: status: configured owner: rein-aharness MessagesOwner (metered-once) with sand-boxer runtime boundary @@ -24,8 +23,8 @@ exec_owner: AGENT_HARNESS_OPS_LABELS: hfact-metered AGENT_HARNESS_OPS_LABELS_MODE: all AGENT_HARNESS_EXECUTION_PROJECT: prj-helixforge-factory - AGENT_HARNESS_REQUIRE_SPEND_ADMISSION: "1" - AGENT_HARNESS_REQUIRE_REQUEST_ADMISSION: "1" + AGENT_HARNESS_REQUIRE_SPEND_ADMISSION: '1' + AGENT_HARNESS_REQUIRE_REQUEST_ADMISSION: '1' AGENT_HARNESS_SPEND_POLICY: /home/tegwick/hfact/owner-metered/spend-policy.json AGENT_HARNESS_SPEND_LEDGER: /home/tegwick/hfact/owner-metered/spend.sqlite3 AGENT_HARNESS_REPO_MAP: '{"hfact-glas-proof":"/home/tegwick/hfact/targets/hfact-glas-proof"}' @@ -36,6 +35,9 @@ exec_owner: /home/tegwick/hfact/owner-metered/owner.json: sha256: 0e263f8299a42f63caf3595ff3eb7adc10354673f7b5125e0811def3bea7c274 private: true + /home/tegwick/hfact/owner-metered/spend-policy.json: + sha256: f31c585916de1ea8bd8e48c72421803dfde1015e6201704b9320f77d2c545d9c + private: true companions: - catalog: activity-core-metered-worker-token field: token diff --git a/docs/evidence/2026-09-27-companion-catalog-readiness.json b/docs/evidence/2026-09-27-companion-catalog-readiness.json new file mode 100644 index 0000000..14b9513 --- /dev/null +++ b/docs/evidence/2026-09-27-companion-catalog-readiness.json @@ -0,0 +1,42 @@ +{ + "date": "2026-09-27", + "task": "SECRETS-WP-0011-T04", + "scope": "Catalog configuration and backend-free recipient validation; not live delivery", + "host": "railiance01", + "activity_core_handoff": "a2eae5f8-60cf-499b-8f52-dd04ac407924", + "owner_check": { + "ok": true, + "check_only": true, + "dispatch_enabled": false, + "messages_policy_sha256": "a7f70cd57536e39b4b4d66c2d52fc6a74ca30eb16a8ea8dc50b59e177c3247fd", + "runtime_sha256": "b6e4e8a429393d68831c996a65c0489664205df969ac9882e581983ec2da4969" + }, + "owner_digest": "46ab4f3fab1c5996ee61c96061b90518d625ffb3fa0966c9bbf7550f422b884b", + "path_and_pin_checks": "passed", + "backend_opened": false, + "file_pins": { + "/home/tegwick/.helixforge-factory/runtimes/b6e4e8a429393d68831c996a65c0489664205df969ac9882e581983ec2da4969/bin/python3": { + "sha256": "e50d468e8b0adfb05733f5b87b3cff34829c4a8c1aea50c865aa8bdfe4bb150f", + "private": false + }, + "/home/tegwick/hfact/owner-metered/owner.json": { + "sha256": "0e263f8299a42f63caf3595ff3eb7adc10354673f7b5125e0811def3bea7c274", + "private": true + }, + "/home/tegwick/hfact/owner-metered/spend-policy.json": { + "sha256": "f31c585916de1ea8bd8e48c72421803dfde1015e6201704b9320f77d2c545d9c", + "private": true + } + }, + "worker_identity": "rein-aharness-metered@railiance01", + "labels": "hfact-metered", + "companion": { + "catalog": "activity-core-metered-worker-token", + "field": "token", + "env": "ACTIVITY_CORE_WORKER_TOKEN" + }, + "credentials_read": false, + "queue_claimed": false, + "provider_request": false, + "native_activation_task": "SECRETS-WP-0009-T03" +} diff --git a/docs/exec-owner-binding.md b/docs/exec-owner-binding.md index 0ddc922..c27e95b 100644 --- a/docs/exec-owner-binding.md +++ b/docs/exec-owner-binding.md @@ -9,8 +9,9 @@ factory recipient is now a pinned metered owner outside the sandbox. A different command or inherited engine credential would violate that boundary. Catalog `delivery_config.exec_owner` is optional for existing lanes. The Claude -factory lane explicitly requires it and currently declares `status: pending`, -`owner` and `reason`. Pending means no exec: refusal precedes approval consumption, +factory lane explicitly requires it. It now has a configured metered recipient +and worker-token companion, validated on 2026-09-27. A pending binding declares +only `status: pending`, `owner` and `reason`. Pending means no exec: refusal precedes approval consumption, backend opening and secret retrieval. Routing stays unready even if custody exists. A reviewed binding uses exactly these keys: diff --git a/docs/glas-claude-delivery.md b/docs/glas-claude-delivery.md index 0b20059..30275f2 100644 --- a/docs/glas-claude-delivery.md +++ b/docs/glas-claude-delivery.md @@ -7,8 +7,8 @@ part of native read-lane adoption. 2026-09-10: the factory continuation uses a metered MessagesOwner outside the sandbox. Its exact runtime is installed and synthetically proved on Railiance. -The catalog now blocks exec with an explicit pending recipient binding until the -native holder and immutable configuration are admitted. See +That initial pending binding has since been replaced by the pinned configuration +reviewed on 2026-09-27; native activation remains separate. See [exec owner binding](exec-owner-binding.md). The older transport description below records the original child-key route; it cannot admit the metered holder. @@ -38,9 +38,10 @@ approval, OpenBao access, provider authentication or production readiness. ## Activation requirements As of 2026-09-27, the shared approval/consume/PDP chain has live evidence from -SECRETS-WP-0010-T03. The Glas catalog still has a pending owner binding and -refuses exec before approval consumption or backend access. The earlier lack -of a served decision path is no longer the current activation blocker. +SECRETS-WP-0010-T03. The Glas catalog now has a configured owner binding and worker companion, +verified by backend-free checks on railiance01. It refuses substituted children +and still requires fresh exact approvals and verified delivery state. The earlier +lack of a served decision path is no longer the current activation blocker. The metered owner configuration and binding were prepared on 2026-09-23. Activity Core reports ACTIVITY-WP-0039 complete on 2026-09-24: custody and the @@ -51,12 +52,11 @@ metered MessagesOwner described in [exec owner binding](exec-owner-binding.md), not the historical sandbox helper above. SECRETS-WP-0009-T03 still owns current recipient/pin admission and the attended -activation. Review the draft binding, revalidate installed files and private -state, configure the approved owner, and obtain exact per-action/per-lane -approvals. Apply the scoped policy/AppRole, verify positive read and denied +activation. Revalidate the configured binding, installed files and private +state in the execution window, and obtain exact per-action/per-lane approvals. Apply the scoped policy/AppRole, verify positive read and denied metadata/sibling/write access with an unrelated negative identity, then prove bounded owner delivery and session revocation. Both lanes must independently -pass approval, PDP, consume and delivery readiness. The handoff and draft are +pass approval, PDP, consume and delivery readiness. The handoff and catalog configuration are not runtime authorization. No production activation was performed in this review. Rotation: store replacement with CAS, stop old runs, verify replacement, revoke diff --git a/docs/railiance-clock-consumer-review.md b/docs/railiance-clock-consumer-review.md new file mode 100644 index 0000000..5c3599c --- /dev/null +++ b/docs/railiance-clock-consumer-review.md @@ -0,0 +1,83 @@ +# Railiance Clock lifecycle-consumer review + +SECRETS-IN-0003, reviewed 2026-09-27. This completes the requested consumer +review. It does not admit a new authority, key, deployment or execution window. + +Reviewed inputs: railiance-clock `61e86a6e01c2e93a9af923a46772a41d080a5743`, +`specs/sample-profile-v0.1.md`, `docs/implementation-review-2026-09-15.md`, +`src/railiance_clock/{protocol,client,admission}.py`, and this repository's +`application_time.py`, approval validators and consume guard. The earlier +intake describes a proposal, but consumer implementation and scoped native +acceptance already exist in [the approval contract](approval-consumption.md) +and [the September 16 receipt](evidence/2026-09-16-t03-completion.json). + +## Signing compatibility and custody + +The reviewed implementation delegates ES256 to PyJWT/cryptography, pinned by +railiance-clock's dependency range (`PyJWT[crypto]>=2.10,<3`). It verifies the +original compact envelope, restricts the algorithm to ES256, and requires an +admitted P-256 public key and exact key ID. Closed headers, 64-byte signatures, +canonical base64url, strict JSON, nonce and authority/environment/epoch/policy +checks accompany signature verification. Secrets-engine uses that library; +it has no independent signing or verification implementation. + +The signing private key belongs to the platform's admitted custody and the +clock authority runtime. It must not share an approval, KeyCape, SSH or engine +credential. No signing key is delivered to this engine: its input is a public +trust binding. The clock's explicit private-file deployment interface is not +proof of OpenBao custody, renewal or fleet-wide admission. Those owner proofs +remain in existing RCLK-WP-0002/0004/0005; no new custody task or lane is created. + +## Bootstrap, rotation and revocation + +Admit the authority, environment, public-key fingerprint, key ID, epoch, policy, +transport and finite lifetime over an independently authenticated owner path. +The sample under verification cannot establish its own trust. HTTPS verification +must work already; the reviewed alternative is explicitly admitted SSH-backed +loopback transport. Neither permits disabling TLS checks or adjusting OS time. + +The current trust admission is client-boot-bound, with a maximum 15-minute +BOOTTIME deadline. Every read obtains a fresh sample and checks the trust file +before and after exchange; cached usable-time holdover is absent. Missing or +changed trust, expiration, an unknown epoch, rollback-state failure or excessive +uncertainty must refuse. The engine's cached client intentionally remains refused +after its trust file changes; restart/reacquisition requires a separately admitted +replacement, not automatic trust discovery. + +For rotation, platform/clock owners admit the replacement key and invalidate old +client trust bindings, then consumers reacquire under the new binding. For +revocation, invalidate every affected binding and stop the old authority before +accepting further samples. File-change detection is local enforcement, not an +automatic estate-wide revocation distribution system. A consumer whose old file +is not invalidated can retain trust until its bounded deadline. Native rotation, +revocation propagation and snapshot/recovery acceptance must be proved by the +owners before claiming those operational guarantees. The library does not grant +permission to reset the persisted rollback floor. + +## Consumer acceptance and limits + +`SECRETS_ENGINE_CLOCK_TRUST_FILE` remains explicit opt-in. When configured, +unavailable or untrusted time refuses; it never falls back to workstation time. +An unconfigured engine retains its existing OS-clock path. Claim validity and +freshness and PDP decision lifetime must contain the whole interval: the lower +bound reaches not-before and the upper bound remains strictly before expiry. +Nanosecond-to-microsecond conversion rounds outward. Decision validity is checked +before and after CAS consume, with the existing actor/tenant/action/field/digest +and policy checks unchanged. A refusal after consume prevents backend access, +although the approval may already be consumed and needs a new request. + +This is a check at the engine's consume boundary, not an atomic transaction +spanning OpenBao or a proof that a long-running child remains within the decision +lifetime. The library provides bounded time evidence, not authorization, spend +admission, complete audit custody or provider-session revocation. Server-side +issuers/approval storage retain their own validity enforcement. + +The September 16 native receipt records three bounded samples, wrong-key-ID +refusal, independent host cross-checks and a 900-second trust admission during +one exact OpenRouter acceptance. It does not prove universal UTC accuracy, +all-platform suspend behavior or a rotation/revocation exercise. Existing +RCLK-WP-0002-T04 and RCLK-WP-0004 retain those owner acceptance boundaries. +`tests/test_application_time.py` covers interval boundaries, missing trust, +no shifted-time mixing and consume expiry refusal. This review accepts the +explicit bounded-time consumer contract within those stated limits; it grants +no new production use. diff --git a/intakes/intakes.md b/intakes/intakes.md index 7f749bf..e0de9e5 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -85,13 +85,20 @@ state_hub_intake_id: "01a0c279-538e-7d99-bf69-f4c67a8d3eda" id: SECRETS-IN-0003 kind: intake title: 'railiance-clock: review signing custody, rotation and lifecycle-consumer adoption condition' -status: open +status: closed origin: cross-repo origin_ref: hub messages f90b9f17 and 302291b5 (railiance-clock, 2026-09-14) priority: low owner: secrets-engine requested_by: railiance-clock -resolution: '' +resolution: >- + Consumer review completed 2026-09-27 in docs/railiance-clock-consumer-review.md. + Reviewed ES256/PyJWT compatibility, platform signing custody boundary, + independent boot-bound trust, rotation/revocation propagation requirements, + interval validity and CAS check-to-use limits. Existing consumer code and + 2026-09-16 scoped native receipt supersede the intake's proposal-only framing. + No new authority/key/activation admitted. Operational acceptance remains with + existing RCLK-WP-0002-T04 and RCLK-WP-0004/0005 owner work. description: >- railiance-clock published its foundation (commit 0a144b6, RCLK-WP plans) and a candidate time-sample profile (commit 7af595b, specs/sample-profile-v0.1.md: diff --git a/scripts/check_layer_conformance.py b/scripts/check_layer_conformance.py index c08070d..ffaa525 100644 --- a/scripts/check_layer_conformance.py +++ b/scripts/check_layer_conformance.py @@ -13,9 +13,9 @@ its own right, reported rather than resolved away by precedence. Layer values are compared against §3's closed four-token vocabulary after an ASCII case-fold (GH-DEC-2026-017 §2-§3, amendment A9). Nothing is re-spelled: `Engine` and `engine` are one token. Neither form carries a standard version -(GH-DEC-2026-017 §5, amendment A12 r2 / GH-DEC-2026-020), and its return is -rejected. The rule reaches content, not a key name: every key and value of the -INTENT.md frontmatter and of layer.yaml is walked, so a versioned `standard:` or +(GH-DEC-2026-017 §5, amendment A12 r3 / GH-DEC-2026-021), and its return is +rejected. The estate reference detector walks every key and value of the +INTENT.md frontmatter and of layer.yaml, so a versioned `standard:` or `companion:` path and a `companion_version` are caught as well as a `standard_version`. Comments and `schema_version` are not reached. Stance maps and evidence classifications (pep-stance.yaml, evidence-classification.yaml) @@ -60,29 +60,51 @@ DECISION_SURFACE = re.compile( LAYER_VOCABULARY = {"taxonomy", "tooling", "engine", "staff"} EXPECTED_LAYER = "engine" -# The standard text this checker was built and validated against, printed on -# every run (GH-DEC-2026-020 §4, A12 r2). v0.7 is the accepted text in force; -# the v0.8 §11 amendments it already applies are named alongside. Bump this -# when the checker is re-validated against a newer accepted text. -VALIDATED_AGAINST = "net-kingdom/canon/standards/security-layer-model_v0.7.md" -AMENDMENTS_APPLIED = "v0.8 A9, A11, A12 r2 (GH-DEC-2026-017, GH-DEC-2026-020)" +# The accepted text and rulings enforced by this run (GH-DEC-2026-021 §2). +# Update together with the reference detector when the accepted text changes. +VALIDATED_AGAINST = ( + "net-kingdom/canon/standards/security-layer-model_v0.7.md (net-kingdom@66dc491) " + "as amended by GH-DEC-2026-017, GH-DEC-2026-020 and GH-DEC-2026-021 " + "(A9-A13, A12 r3; gate-house@39d9287)" +) SCOPE = ( "declaration = INTENT.md frontmatter + layer.yaml (every key and value); " "source = src/secrets_engine/**/*.py; " "not reached by A12: pep-stance.yaml, evidence-classification.yaml" ) -# A12 r2: a version of the standard or its companion, anywhere in the -# declaration. `schema_version` is the declaration file's own schema and is -# not reached. -VERSION_KEY = re.compile(r"version", re.IGNORECASE) -UNREACHED_KEYS = {"schema_version"} -VERSIONED_REF = re.compile( - r"(?i)(security-layer-model|security-companion|layer-model|companion)" - r"[^\s]*?(?:[_@-]v?\d+(?:\.\d+)*|\bv\d+(?:\.\d+)*)" -) -STANDARD_KEYS = {"standard", "companion", "framework"} -BARE_VERSION = re.compile(r"(?i)(?:^|[_@\s-])v?\d+\.\d+(?:\.\d+)*\b") +# Estate reference detector, GH-DEC-2026-021 §3. +VERSION_KEY = re.compile(r"(standard|companion).*version|version.*(standard|companion)", re.I) +VERSION_IN_VALUE = re.compile(r"[_\-.]v\d+(\.\d+)*(\.md)?\b|@v?\d+\.\d+", re.I) +NOT_REACHED_KEYS = {"schema_version"} +IDENTITY_KEYS = {"standard", "companion"} +IDENTITY_VERSION = re.compile(r"\bv?\d+\.\d+", re.I) + + +def find_version_pins(node, where: str = "", identity: bool = False) -> list[str]: + """Every place in a parsed declaration that carries a standard/companion version. + + Walks every key and value (comments are gone after parsing, which is the + A12 r2 exclusion). Returns human-readable locations; empty means clean. + """ + pins: list[str] = [] + if isinstance(node, dict): + for k, v in node.items(): + here = f"{where}.{k}" if where else str(k) + if str(k) in NOT_REACHED_KEYS: + continue + if VERSION_KEY.search(str(k)): + pins.append(f"{here} (key names a standard/companion version)") + continue + pins.extend(find_version_pins(v, here, str(k).lower() in IDENTITY_KEYS)) + elif isinstance(node, list): + for i, v in enumerate(node): + pins.extend(find_version_pins(v, f"{where}[{i}]", identity)) + elif isinstance(node, str) and VERSION_IN_VALUE.search(node): + pins.append(f"{where} = {node!r} (value carries a version)") + elif isinstance(node, str) and identity and IDENTITY_VERSION.search(node): + pins.append(f"{where} = {node!r} (identity-bearing value carries a version)") + return pins def _fold(value: object) -> str: @@ -90,37 +112,13 @@ def _fold(value: object) -> str: return str(value).strip().encode("ascii", "ignore").decode().lower() -def _version_hits(data: object, path: str = "") -> list[str]: - """Every place a standard or companion version appears in a declaration.""" - hits: list[str] = [] - if isinstance(data, dict): - for key, value in data.items(): - here = f"{path}.{key}" if path else str(key) - if str(key) in UNREACHED_KEYS: - continue - if VERSION_KEY.search(str(key)): - hits.append(f"key {here!r}") - continue - if isinstance(value, str) and str(key).lower() in STANDARD_KEYS: - if BARE_VERSION.search(value): - hits.append(f"{here}: {value!r}") - continue - hits.extend(_version_hits(value, here)) - elif isinstance(data, list): - for n, item in enumerate(data): - hits.extend(_version_hits(item, f"{path}[{n}]")) - elif isinstance(data, str) and VERSIONED_REF.search(data): - hits.append(f"{path}: {data!r}") - return hits - - def _no_standard_version(where: str, data: dict) -> None: - hits = _version_hits(data) + hits = find_version_pins(data) if hits: print( f"MALFORMED: {where} carries a standard or companion version at " f"{'; '.join(hits)} — a layer declaration MUST NOT carry one in any " - "key or value (§11 as amended by A12 r2, GH-DEC-2026-020 §1-§2)" + "key or value (§11 as amended by A12 r3, GH-DEC-2026-021 §1-§3)" ) raise SystemExit(2) @@ -231,7 +229,7 @@ def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--report", action="store_true") args = ap.parse_args() - print(f"validated against: {VALIDATED_AGAINST} [{AMENDMENTS_APPLIED}]") + print(f"validated against: {VALIDATED_AGAINST}") print(f"scope: {SCOPE}") front = intent_frontmatter() diff --git a/tests/test_exec_owner.py b/tests/test_exec_owner.py index f11523f..da90d62 100644 --- a/tests/test_exec_owner.py +++ b/tests/test_exec_owner.py @@ -102,8 +102,9 @@ def test_binding_changed_after_approval_refuses_before_fetch(bound, monkeypatch) exec_delivery.exec_with_secret(object(), entry, "api_token", data["delivery_config"]["exec_owner"]["command"], expected_owner_digest=expected) -def test_pending_real_catalog_refuses_before_approval_and_backend(tmp_path, monkeypatch): +def test_pending_owner_refuses_before_approval_and_backend(tmp_path, monkeypatch): entry = load_entry(Path(__file__).resolve().parents[1] / "catalog/glas-claude-agent-dev-anthropic.yaml") + entry.delivery_config["exec_owner"] = {"status": "pending", "owner": "fixture", "reason": "not admitted"} monkeypatch.setattr(cli, "get_entry", lambda *a: entry) for name in ["_require_lane_approval", "_open_backend"]: monkeypatch.setattr(cli, name, lambda *a, **k: pytest.fail("no approval consume or backend")) @@ -160,8 +161,35 @@ def test_invalid_binding_is_not_a_catalog_fallback(bound, change): def test_pending_owner_never_advertises_ready(tmp_path, monkeypatch): from secrets_engine import routing entry = load_entry(Path(__file__).resolve().parents[1] / "catalog/glas-claude-agent-dev-anthropic.yaml") + entry.delivery_config["exec_owner"] = {"status": "pending", "owner": "fixture", "reason": "not admitted"} monkeypatch.setattr(routing, "resolve_decision", lambda **k: SimpleNamespace(status="approved", review_url="", is_approved=lambda: True)) client = SimpleNamespace(is_reachable=lambda: True, read_policy=lambda p: "policy", approle_exists=lambda r: True, kv_fields_present=lambda *a: {"ANTHROPIC_API_KEY": True}) result = routing.route_lane(entry, hub_url="", repo_root=tmp_path, client=client) assert not result.ready and "exec owner" in result.missing assert "