Add the v1 portable core contract validator (inactive) - #183
Conversation
Adds the smallest vendor-neutral record family later adapters can share: core/v1/contracts.jq (pure jq validator for five document kinds, three capabilities, and five permissions) plus its shell front door scripts/core-contract.sh and its hermetic test suite. This is a manual, inactive validator — nothing in the live profile or /yshifu calls it yet. Unattended agents cannot write constitution paths directly, so the plan's CI wiring (jq 1.6 pin + new test step) and AGENTS.md update are saved as unified diffs under proposals/ for the operator to review and apply by hand; the new test suite does not run in this PR's own CI until then. Implements the accepted work/portable-core-contracts spec and plan.
Codex reviewer (cross-vendor, read-only)Reviewed-head: 84633c9 Posted verbatim by The validator rejects valid reviewer requests while accepting numerous invalid profile, request, and result relations, including unsupported manifest capabilities and proofless passing results. Its regression suite is also only present as an unapplied CI proposal. Full review comments:
|
Manager fix claim
Exact tuple
Round-0 Codex review was a not-pass (comment 5463604326, Reviewed-head |
Codex's round-0 review (comment 5463604326) found the validator implementing several relations as shape/presence checks rather than the closure and exact equality the spec requires. Per finding: - reviewer change-base comparison compared a present<> wrapper against a bare revision, so it could never match even a correct request - delivered scopes (finish_condition/verification_instruction/capability scope) now close over the request's named inputs and check payload media type - every Git ref in target/base/source/inputs/reviewer-change now must use target_repository_id - verifier candidates must now be a Git tree, not any git-object - profile-set binding/manifest relation now checks the manifest actually offers the binding's role, execution kind, capability, and permissions - completed-result required_evidence_kinds is now an exact match (closes the all([]; ...) vacuous-truth gap that let zero-evidence "passed" results through) - conclusive-execution match widened to implementation, manifest ref, config, authority, performer identity/boundary, and environment (previously compared only a handful of fields and tools by ID only) - completed-inconclusive now forbids outputs/delta and requires reason - completed producer change now requires exactly one output, not "at least one" - resolved profile/manifest source digests now must equal the supplied documents' real digests - profile-set now rejects manifests unreferenced by any binding (and the positive test that asserted the old, wrong "extras allowed" behavior is fixed to assert exactness instead) - stale-observation selectors now derive the expected value from the request and require the observed value to actually differ - set<T> canonical ordering (sorted-by-key) is now enforced, not just uniqueness - timestamps are now validated as real UTC ranges, not just digit shape - absent target now requires the bootstrap risk tier, not merely a producer role (the shipped positive fixture is updated to use it) While implementing the stale-observation fix, found and fixed three more instances of the same defect class (`array | index(.field)`, which reads the field off the piped array instead of the original value): is_stale_selector crashed with E_RUNTIME for 6 of its 8 selector kinds, is_document_kind always returned true regardless of input, and is_observed_capability's "unclassified" branch crashed whenever exercised. Also fixed two jq scoping bugs of my own introduction (delivered_scope_input_ok/stale_observation_ok needed $-bound value params, not filter params, since they're called from inside all(.[]; ...) and a filter param gets re-evaluated against the inner "." instead of staying pinned to the value passed at the call site). Every fix has a mutation row proving rejection; fixtures were updated where a fix required a genuinely valid positive document (bootstrap risk tier, exact manifest set, canonical sort order, matching delivered-scope subjects, and a resolved binding built from the same object as its actual/performer execution record instead of two independently hand-typed literals). Not touched: the CI-wiring finding (parked in proposals/ for the operator; see PR discussion) and the accepted plan/spec, which are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deploying ystack with
|
| Latest commit: |
ab4a708
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://cd7665e3.fabrica-6yx.pages.dev |
| Branch Preview URL: | https://ystack-impl-portable-core-co.fabrica-6yx.pages.dev |
Round-0 fix: closed the review gapsCommit: Per-finding dispositionAll P1/P2 code and test findings from comment 5463604326 are fixed, each with a new mutation row that fails without the fix (
Not touched: the CI-wiring finding is correct and stays parked in Bugs found and fixed incidentallyWhile implementing the stale-observation fix I found the same defect class in three other spots —
Also fixed two jq scoping bugs in my own new code: Fixture changesA few fixes required the shipped positive document to become genuinely valid rather than incidentally accepted:
Proof (commit
|
| File | Before | After | Δ |
|---|---|---|---|
core/v1/contracts.jq |
739 | 882 | +143 |
scripts/core-contract.sh |
131 | 131 | 0 |
scripts/test/core-contract.test.sh |
381 | 448 | +67 |
scripts/test/core-contract-fixtures.jq |
132 | 155 | +23 |
| Total | 1,383 | 1,616 | +233 |
This round's growth is entirely closing proof gaps the reviewer named (widened relations, exact-match closures, canonical ordering) plus mutation-row coverage for each — no new concern, no compressed code, no dropped tests. It further widens the PR's total distance from the accepted 900–1,100 review-size range; that distance was already flagged and accepted as an explicit exception at G2/plan gate, and every addition here is required proof, not padding.
Not merging, not approving, not touching claimed — leaving that to yshifu.
Codex reviewer (cross-vendor, read-only)Reviewed-head: a11d9f0 Posted verbatim by The validator accepts several records forbidden by the accepted contract and rejects valid completed-inconclusive incident records. Its new test suite is also not wired into the active CI workflow. Full review comments:
|
Manager fix claim
Exact tuple
Round-1 Codex review was a not-pass (comment 5463851247, Reviewed-head |
Fixes the round-0-introduced regression where completed-execution equality was widened unconditionally, rejecting valid completed-inconclusive incident records; scopes it back to completed non-inconclusive results only. Also makes an unavailable requested provider/model/effort/prompt/skills fact force a completed result inconclusive, checks command form before runtime dependencies in the CLI front door (so a bad command reports E_USAGE even without the pinned jq), counts object keys in the decoded-string limit walker, validates real calendar dates (month-specific day counts, leap years), groups resolved source claims by Git object so one object gets one format/digest claim, forces every dormant adapter role deterministic, and pins the resolved- profile ID in a stale observation. Adds a mutation+acceptance test pair per guard, per round-1 review comment 5463851247 on PR #183.
Round-1 fix: findings closed, proof re-run on
|
| file | total (wc -l) | non-blank | prior non-blank | delta |
|---|---|---|---|---|
core/v1/contracts.jq |
930 | 829 | 786 | +43 |
scripts/core-contract.sh |
138 | 123 | 117 | +6 |
scripts/test/core-contract-fixtures.jq |
168 | 154 | 142 | +12 |
scripts/test/core-contract.test.sh |
534 | 503 | 418 | +85 |
| total | 1770 | 1609 | 1463 | +146 |
This round's growth (+146 non-blank lines) is the same shape as round-0's: closing eight named findings, each with a real fix plus a mutation+acceptance test pair (per this round's explicit testing rule, since round-0's regression was a rejection-only-test gap). No new concern or scope was added — every line traces to one of the nine numbered findings above. We're well past the 900-1,100 accepted-exception range now (as flagged going into this round); I did not compress code or drop tests to fight that number, per the brief.
What I deliberately did not change
.github/workflows/ci.yml(the CI-wiring P1) — constitution path, stays inproposals/.- Accepted
intent.md/spec.md/plan.md— untouched, hashes unchanged.
Codex reviewer (cross-vendor, read-only)Reviewed-head: cbfa4f3 Posted verbatim by The validator accepts resolved profiles and conclusive model results that violate core capability, role-separation, and tool-observation guarantees. The new contract suite is also not wired into the actual CI workflow, and the wrapper does not preserve its error contract on temporary-directory failures. Full review comments:
|
|
Why this is stopping hereThree Codex review rounds are done. The findings are converging steadily — actionable
The blocking item is not the coder's to fixCodex has raised the same P1 in all three rounds: This is correct behaviour by the rail, not a defect — but it means green CI on this PR What is still open in the codeTwo security-class P1s and two P2s, all in the accepted scope — none of them a disagreement
Can this scope down instead? My answer is noThe productive way to resolve a cap is normally to ship the converged core and split the Size has materially exceeded the accepted rangeThe bridge pinned Actual trajectory: ~1,234 → ~1,467 → 1,609 non-blank lines — about 46% above the Decisions needed
The attempt is preserved exactly as it stands: branch |
Operator ruling — exact terminal scopeThe operator explicitly authorized this exact package in the active task:
No other scope is authorized. There is no automatic round-4. Re-run the complete proof, require the real validator suite in CI, and obtain a fresh native Codex review on the final exact head/base. The operator remains the only merge authority. |
Manager fix claim — terminal round-3
Exact tuple
Operator disposition verified, not assumedHead moved Claim created while |
Applies the operator-authorized terminal round-3 revision (PR #183, comment 5464063082), limited to the four findings in Codex review comment 5464015820: - core/v1/contracts.jq: apply the capability and protected-role profile invariants to a resolved profile's own embedded binding set (resolved_bindings_profile_invariants_ok, reusing the existing profile_protected_roles_ok / profile_binding_capability_ok predicates), wired into both validate-document and validate-stage-run so a stage run can no longer be approved for an operation its binding never actually requested. - core/v1/contracts.jq: route metadata.tools through the same metadata_requested_unavailable path already used for provider/model/effort/ prompt/skills, so a completed result whose actual tool use cannot be established can no longer stay conclusive. - scripts/core-contract.sh: handle a failing `mktemp -d` explicitly as E_RUNTIME instead of letting `set -e` exit past `fail` and leak mktemp's raw diagnostic/path. - core/v1/contracts.jq: extend RepoPath's control-character rejection to DEL (U+007F) and the C1 range (U+0080-U+009F), using jq's \x{...} codepoint syntax since bare \xHH only matches single-byte input under UTF-8 mode. Adds paired reject/accept regression tests for all four in scripts/test/core-contract.test.sh (191 -> 279 assertions). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Terminal round-3 revision — doneCommit tested/pushed: Scoped to exactly the four findings authorized in the operator ruling (comment 5464063082) from Codex review comment 5464015820. No other changes. Per-finding disposition
Proof (all run on commit
|
| File | Before (raw lines) | Net delta | After (raw lines) |
|---|---|---|---|
core/v1/contracts.jq |
930 | +22 (+30/-8) | 952 |
scripts/core-contract.sh |
138 | +3 (+4/-1) | 141 |
scripts/test/core-contract.test.sh |
534 | +65 (+65/-0) | 599 |
| Total | +90 |
No other file changed (confirmed via git diff --stat / git status). No compression, shortened lines, or dropped tests — every added line is either the direct fix plumbing for one of the four findings or a paired reject/accept regression test for it (191 → 279 assertions). Per the operator ruling, this growth is explained under the existing review_size: accepted-exception and does not amend the accepted intent/spec/plan, bridge identity, or the recorded 900–1,100 range.
Observations deliberately not acted on (out of scope for this terminal round)
- None. The four authorized findings were the only issues in scope, and no other defect was noticed while making these fixes.
This is the terminal round. Round bumped round-2 → round-3 (verified added/absent). claimed retained. Not merging — operator merge only.
Codex reviewer (cross-vendor, read-only)Reviewed-head: ab4a708 Posted verbatim by The validator accepts malformed resolved profiles and impossible verifier requests, and it rejects the specified failed outcome for completed checks. The shell wrapper also violates its sanitized error contract on temporary-storage failures. Full review comments:
|
|
Terminal round-3 did not passThe operator-authorized terminal round-3 revision is complete and the fresh Codex review Per the operator ruling there is no automatic round-4, so this returns to What went right
What the four findings actually areI checked each against the round-3 diff rather than taking the labels at face value. Two are incomplete closures of authorized findings, not new scope.
One is a latent defect from the round-1 fix, surfacing now — not a round-3 regression.
One is genuinely new and was not among the authorized four.
The honest read on trajectoryActionable findings went 15 → 8 → 4 → 4. This round plateaued rather than converged, and That is a signal about the artifact, not about coder effort: a single ~950-line jq validator Decisions needed
State preserved exactly: branch |
Closes #155
What changed
Adds the v1 portable core contract: the smallest vendor-neutral record family
later adapters can share, and a pure validator for it.
core/v1/contracts.jq— the only product source for v1 shapes, thecapability/permission registries, and relational validation (manifest ↔
profile ↔ resolved-profile, request ↔ resolved-profile ↔ result). It never
reads Git, launches a process, calls a model, or proves a claim true — it
only checks that caller-supplied documents are internally consistent.
scripts/core-contract.sh— the one public front door:validate-document,validate-profile-set, andvalidate-stage-run. It snapshots bounded inputbytes, requires jq 1.6 exactly, checks byte-for-byte canonical JSON, hashes
the accepted bytes, and hands a driver value (parsed JSON + digests, never a
raw path) to
contracts.jq. Success is exit 0 with empty output; failure isone allowlisted
E_*token on stderr.scripts/test/core-contract-fixtures.jq+scripts/test/core-contract.test.sh— a hermetic test suite. The test script independently canonicalizes and
hashes each fixture document with pinned jq and an external SHA tool (never
the validator itself), wiring each digest into the next document's ref in
dependency order (manifest → profile → resolved profile → request → result)
so no fixture digest is ever self-referential.
ci/required-files.txt,README.md,RESTORE.md— the new files arerestore-critical and documented; the validator is explicitly called out as
manual and inactive (nothing in the live profile or
/yshifucalls it).Why
This is the accepted
portable-core-contractsspec/plan(
work/portable-core-contracts/{intent,spec,plan}.md, all read-only in thisPR): a small, machine-checked vocabulary so later adapter/profile-resolution
work has one shared, tested schema instead of drifting per implementation.
Nothing here activates a profile, extracts a real adapter, or changes any
current behavior.
Why CI can't run the new suite yet (
proposals/)I'm an unattended coder, and unattended agents cannot write constitution
paths (
.github/**,AGENTS.md, etc.) directly — only an operator-drivensession can. The plan's CI wiring and doc update are both against
constitution paths, so they're saved as unified diffs instead of applied:
proposals/portable-core-contracts-ci-jq16.patch— adds one new CI step to.github/workflows/ci.yml: download the official jq 1.6 Linux release,verify its pinned SHA-256, assert
jq-1.6, and runscripts/test/core-contract.test.sh. Every existing CI step is unchanged.proposals/portable-core-contracts-agents-md.patch— replaces AGENTS.md's"validators are still to come" line with the new files and the exact local
test command, keeping the existing jq/shellcheck safety guidance intact.
Both patches were verified with
git apply --checkagainst this PR's head.Because of this, the new test suite does not run in this PR's own CI —
it only runs locally (see proof below). It starts running in CI once the
operator applies
proposals/portable-core-contracts-ci-jq16.patchby hand.How I tested it
Commit tested:
84633c95a1c3dddd16a4cc91c698a241d0ff4549Pinned jq 1.6 (
jq-osx-amd64through Rosetta on this Apple Silicon host, SHA-256verified) and shellcheck 0.11.0 (matches
SHELLCHECK_VERSIONin.github/workflows/ci.yml; this host's shellcheck was already exactly 0.11.0).86 distinct positive/mutation cases (191 individual assertions), well over the
required 60 — covering: the full positive path (every document kind,
profile-set, stage-run); CLI usage/arity including the 1–8 manifest boundary;
raw-byte/canonical edges (empty, multi-root, BOM, invalid UTF-8, duplicate
keys, alternate whitespace/escaping, missing/extra final LF, unsorted keys);
exact-boundary and one-over cases for byte size (1,048,576), depth (32),
member count (256), and string length (8,192), plus floats/negatives/oversize
integers; manifest/profile/resolved-profile/stage-request/stage-result shape
rules; profile-set and stage-run relations (tool/config mismatches, role
separation, missing/duplicate manifests, wrong binding, evidence-role rules,
the terminal-status presence matrix, time ordering); a PATH-stubbed non-1.6 jq
rejected as
E_RUNTIME; theshasum -a 256fallback whensha256sumisabsent, and a missing-SHA-tool failure; and that stderr never echoes an input
path or a distinctive fixture byte.
git diff --checkon the actual diff reports exactly two hits, bothexpected and harmless: a single blank context line inside each committed
.patchfile (a real, unchanged blank line inci.yml/AGENTS.md, shown inunified-diff form with its required leading space). That is normal unified-diff
syntax, not trailing whitespace in source; "fixing" it would corrupt the patch
and break
git apply. Both patches were re-verified withgit apply --checkafter the commit.
Line count vs. the accepted 900–1,100 range
Per-file, counting real content lines (non-blank, non-comment-only for new
code files; net added lines for edited docs; real
+-content lines, not diffheaders, for the two patches):
core/v1/contracts.jqscripts/core-contract.shscripts/test/core-contract-fixtures.jqscripts/test/core-contract.test.shREADME.md(net added)RESTORE.md(net added)ci/required-files.txt(net added)proposals/…-ci-jq16.patch(real+content)proposals/…-agents-md.patch(real+content)wc -l/git diff --stat)This is above the bridge's authoritative 900–1,100 range on both countings.
Breaking it down against the plan's own per-area estimate:
contracts.jq(601 SLOC) and the shell wrapper (95 SLOC) are within/neartheir own estimated ranges (380–755, 50–110).
is because I built 86 distinct test cases (191 assertions) — comfortably
over the required "at least 60" — with real, hand-crafted raw-byte fixtures
for the exact-boundary/one-over limit cases (which need precisely-sized
byte payloads, not just short strings) and independent digest-chaining
across all five fixture documents, exactly as the plan's step 5 and the
spec's "fixture circularity" risk require (never reusing the validator as
its own test oracle).
estimate almost entirely because of the two
proposals/patches (41 SLOCof real content): that packaging overhead exists only because this is an
unattended run routing constitution-path changes through
proposals/instead of editing them directly, per
AGENTS.md.I did not compress code, shorten lines, or drop any test to move this number
down — every line here is either real validator behavior, a real test case,
or the mandated
proposals/packaging. If the size itself is a concern, Idefer to review/operator judgment on whether to accept it as explained or
split further.
Scope
Implements only the accepted intent (
work/portable-core-contracts/intent.md),spec (
spec.md), and plan (plan.md) — all three are read-only in this PR.No profile activation, no real adapter, no Git reads, no forge/CI/execution/
identity/publisher operations, no
/yshifuor manager-persona change.