From 05fd2d93ad3fee46d72286b416d8e927fae862c9 Mon Sep 17 00:00:00 2001 From: kikashy Date: Thu, 22 Oct 2026 15:08:10 -0400 Subject: [PATCH 1/3] Limit RFC 0020's later-sibling rule to admitted instances Qualify the limit-exceeded later-sibling rule so projection and influence admission failures retain their own codes. Add hand-written positive and negative workflow vectors that exercise both rules and the verifier finding. Co-Authored-By: Codex Signed-off-by: kikashy --- CHANGELOG.md | 2 + conformance/workflow/README.md | 4 ++ conformance/workflow/manifest.json | 3 ++ rfcs/0020-workflow.md | 14 ++++-- schema/workflow-1.schema.json | 48 ++++++++++++++++++- spec/workflow.md | 14 ++++-- tests/test_workflow.py | 77 ++++++++++++++++++++++++++++++ 7 files changed, 153 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61a94a4..6e1da1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ here. ### Added +- RFC 0020 limits the later-sibling `limit-exceeded` rule to instances admitted for acquisition; + admission failures keep their own codes, with a recorded positive vector and negative twin (#213). - Document ceiling profiles (#232): default `base` and declared `extended`, a shared ceiling table, explicit unsupported-profile refusal, capability discovery and byte identity. Schemas retain base bounds; profile conformance covers negotiation. diff --git a/conformance/workflow/README.md b/conformance/workflow/README.md index d34e7cb..6462502 100644 --- a/conformance/workflow/README.md +++ b/conformance/workflow/README.md @@ -10,6 +10,10 @@ Each summary's `recordsDigest` was fixed independently as SHA-256 of the canonic its stage's dispositions in recorded order. The repository test reconstructs those exact bytes, recomputes every summary counter and digest, redraws the sample, and reconstructs the complete drift result. The vectors are deliberately authored inputs, not output captured from an executor. +The later-sibling pair records an admitted instance, the instance that exceeds the record limit, +a projection failure that keeps `projection-invalid`, and an admitted later sibling that records +`limit-exceeded`; its negative twin changes only the projection failure and expects the verifier's +`inventory-mismatch` finding. This fixture establishes no implementation, execution, authentication, verdict or conformance claim. The full research recording described by companion §11 remains future work. diff --git a/conformance/workflow/manifest.json b/conformance/workflow/manifest.json index 2119f6a..2373f1d 100644 --- a/conformance/workflow/manifest.json +++ b/conformance/workflow/manifest.json @@ -12,7 +12,10 @@ { "path": "reports/sample-size-over-bound.json", "schemaValid": true }, { "path": "reports/sample-seed-invalid.json", "schemaValid": true }, { "path": "reports/summary-mismatch.json", "schemaValid": true }, + { "path": "reports/later-sibling-admission-wrong-failure.json", "schemaValid": true }, { "path": "vectors/sample-size-over-bound.json", "schemaValid": true }, + { "path": "vectors/later-sibling-admission.json", "schemaValid": true }, + { "path": "vectors/later-sibling-admission-wrong-failure.json", "schemaValid": true }, { "path": "vectors/sample-seed-invalid.json", "schemaValid": false }, { "path": "vectors/sample-seed-empty.json", "schemaValid": false }, { "path": "vectors/sample-seed-missing.json", "schemaValid": false }, diff --git a/rfcs/0020-workflow.md b/rfcs/0020-workflow.md index 0f0c5ff..963c9e6 100644 --- a/rfcs/0020-workflow.md +++ b/rfcs/0020-workflow.md @@ -277,9 +277,11 @@ containers and unsuccessful work; nothing is truncated to fit. The same bounds h bytes and input bytes. A workflow runner refuses admission before acquisition when a known bound fails. Account for results in instance order: stage order, then selected parent-record order, then output index, regardless of acquisition or completion order. The first instance whose -outputs would exceed a branch bound and every later instance of that stage record -`limit-exceeded`, emitting no outputs. Earlier completed siblings' outputs remain in the closed -set with those failures explicit. Nothing is truncated to fit. +outputs would exceed a branch bound and every later instance of that stage admitted for acquisition +record `limit-exceeded`, emitting no outputs. Under §§3 and 7, an instance that fails projection or +influence admission is not admitted for acquisition and keeps its own failure code regardless of +its position in the stage. Earlier completed siblings' outputs remain in the closed set with those +failures explicit. Nothing is truncated to fit. ### 3. Stages and projected cases @@ -937,7 +939,11 @@ revisions or relations, adding an unselected child, omitting or repeating a sele laundering generated influence. Include a record-class downstream operation parameterised from a generated payload without generated admission, refused before acquisition, including an affected case parameter its request never names. Include two siblings of six outputs when ten records -remain, completed in both orders: the second and every later sibling record `limit-exceeded`. +remain, completed in both orders: the second and every later admitted sibling record +`limit-exceeded`. In one stage record, in order, an admitted completed instance, an admitted +`limit-exceeded` instance, a `projection-invalid` instance and an admitted later sibling that +records `limit-exceeded`; mutate the projection failure to `limit-exceeded` for an +`inventory-mismatch` twin. Include interruption after atomic admission, uncertain acquisition, a recorded selection bound refusal, and retries before and after consumption. Freeze expected reports with the recordings. Add integer and named family-3 variants with matching published identities, and refuse changed diff --git a/schema/workflow-1.schema.json b/schema/workflow-1.schema.json index 96c36ff..daec805 100644 --- a/schema/workflow-1.schema.json +++ b/schema/workflow-1.schema.json @@ -11,7 +11,8 @@ { "$ref": "#/$defs/drift" }, { "$ref": "#/$defs/bundleFixture" }, { "$ref": "#/$defs/fixtureManifest" }, - { "$ref": "#/$defs/refusalVector" } + { "$ref": "#/$defs/refusalVector" }, + { "$ref": "#/$defs/instanceStateVector" } ], "$defs": { "id": { @@ -805,6 +806,51 @@ }, "additionalProperties": false }, + "instanceStateVector": { + "type": "object", + "required": ["vectorVersion", "kind", "stage", "maxRecords", "instances"], + "properties": { + "vectorVersion": { "const": 1 }, + "kind": { "const": "later-sibling-admission" }, + "stage": { "$ref": "#/$defs/id" }, + "maxRecords": { "$ref": "#/$defs/positiveCount" }, + "instances": { + "type": "array", + "minItems": 4, + "maxItems": 4, + "items": { + "type": "object", + "required": ["id", "admission", "requestedOutputs", "record"], + "properties": { + "id": { "$ref": "#/$defs/id" }, + "admission": { "enum": ["admitted", "projection-invalid"] }, + "requestedOutputs": { "$ref": "#/$defs/count" }, + "record": { + "type": "object", + "required": ["caseDigest", "state", "failure", "attempts"], + "properties": { + "caseDigest": { + "oneOf": [{ "$ref": "#/$defs/digest" }, { "type": "null" }] + }, + "state": { "enum": ["completed", "failed"] }, + "failure": { + "oneOf": [ + { "type": "null" }, + { "enum": ["projection-invalid", "limit-exceeded"] } + ] + }, + "attempts": { "$ref": "#/$defs/count" } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "expectedFinding": { "const": "inventory-mismatch" } + }, + "additionalProperties": false + }, "refusalVector": { "oneOf": [ { diff --git a/spec/workflow.md b/spec/workflow.md index c556ebd..43ad3f5 100644 --- a/spec/workflow.md +++ b/spec/workflow.md @@ -99,9 +99,11 @@ containers and unsuccessful work; nothing is truncated to fit. The same bounds h bytes and input bytes. A workflow runner refuses admission before acquisition when a known bound fails. Account for results in instance order: stage order, then selected parent-record order, then output index, regardless of acquisition or completion order. The first instance whose -outputs would exceed a branch bound and every later instance of that stage record -`limit-exceeded`, emitting no outputs. Earlier completed siblings' outputs remain in the closed -set with those failures explicit. Nothing is truncated to fit. +outputs would exceed a branch bound and every later instance of that stage admitted for acquisition +record `limit-exceeded`, emitting no outputs. Under §§3 and 7, an instance that fails projection or +influence admission is not admitted for acquisition and keeps its own failure code regardless of +its position in the stage. Earlier completed siblings' outputs remain in the closed set with those +failures explicit. Nothing is truncated to fit. ## 3. Stages and projected cases @@ -759,7 +761,11 @@ revisions or relations, adding an unselected child, omitting or repeating a sele laundering generated influence. Include a record-class downstream operation parameterised from a generated payload without generated admission, refused before acquisition, including an affected case parameter its request never names. Include two siblings of six outputs when ten records -remain, completed in both orders: the second and every later sibling record `limit-exceeded`. +remain, completed in both orders: the second and every later admitted sibling record +`limit-exceeded`. In one stage record, in order, an admitted completed instance, an admitted +`limit-exceeded` instance, a `projection-invalid` instance and an admitted later sibling that +records `limit-exceeded`; mutate the projection failure to `limit-exceeded` for an +`inventory-mismatch` twin. Include interruption after atomic admission, uncertain acquisition, a recorded selection bound refusal, and retries before and after consumption. Freeze expected reports with the recordings. Add integer and named family-3 variants with matching published identities, and refuse changed diff --git a/tests/test_workflow.py b/tests/test_workflow.py index e9187f2..e5d7ccb 100644 --- a/tests/test_workflow.py +++ b/tests/test_workflow.py @@ -148,6 +148,14 @@ def test_two_stage_plan_carries_bulk_and_refusal_vectors(self) -> None: self.assertIn("For every new refusal code, retain a valid expected-report", unwrapped) self.assertIn("Interrupted and resumed variants retain", unwrapped) + def test_later_sibling_rule_is_limited_to_admitted_instances(self) -> None: + companion = (ROOT / "spec" / "workflow.md").read_text(encoding="utf-8") + section = companion.split("\n## 2. ", 1)[1].split("\n## 3. ", 1)[0] + unwrapped = section.replace("\n", " ") + self.assertIn("every later instance of that stage admitted for acquisition", unwrapped) + self.assertIn("Under §§3 and 7", unwrapped) + self.assertIn("keeps its own failure code", unwrapped) + class WorkflowFixtureTests(unittest.TestCase): @classmethod @@ -275,6 +283,75 @@ def test_recorded_sample_is_redrawn_from_seed(self) -> None: sample = json.loads((FIXTURE / "sample.json").read_text(encoding="utf-8")) self.assertEqual(sample["selected"], redraw_sample(sample)) + def test_later_sibling_vector_preserves_admission_failures(self) -> None: + def verify(vector: dict[str, object]) -> dict[str, str]: + remaining = vector["maxRecords"] + assert isinstance(remaining, int) + later_sibling_rule = False + for instance in vector["instances"]: + admission = instance["admission"] + requested = instance["requestedOutputs"] + if admission == "projection-invalid": + expected = {"state": "failed", "failure": "projection-invalid"} + elif later_sibling_rule or requested > remaining: + expected = {"state": "failed", "failure": "limit-exceeded"} + later_sibling_rule = True + else: + expected = {"state": "completed", "failure": None} + remaining -= requested + actual = { + "state": instance["record"]["state"], + "failure": instance["record"]["failure"], + } + if actual != expected: + return {"result": "finding", "finding": "inventory-mismatch"} + return {"result": "reading", "consistency": "agrees"} + + accepted = json.loads( + (FIXTURE / "vectors" / "later-sibling-admission.json").read_text(encoding="utf-8") + ) + wrong = json.loads( + (FIXTURE / "vectors" / "later-sibling-admission-wrong-failure.json").read_text( + encoding="utf-8" + ) + ) + self.assertEqual( + [ + ("admitted", "completed", None), + ("admitted", "failed", "limit-exceeded"), + ("projection-invalid", "failed", "projection-invalid"), + ("admitted", "failed", "limit-exceeded"), + ], + [ + (row["admission"], row["record"]["state"], row["record"]["failure"]) + for row in accepted["instances"] + ], + ) + self.assertEqual(verify(accepted), {"result": "reading", "consistency": "agrees"}) + report = json.loads( + (FIXTURE / "reports" / "later-sibling-admission-wrong-failure.json").read_text( + encoding="utf-8" + ) + ) + self.assertEqual(wrong["expectedFinding"], "inventory-mismatch") + self.assertEqual(verify(wrong), report) + self.validator.validate(report) + self.assertEqual( + {"caseDigest": None, "attempts": 0}, + { + member: accepted["instances"][2]["record"][member] + for member in ("caseDigest", "attempts") + }, + ) + self.assertEqual( + wrong["instances"][2]["record"]["failure"], + "limit-exceeded", + ) + repaired_twin = json.loads(json.dumps(wrong)) + repaired_twin.pop("expectedFinding") + repaired_twin["instances"][2]["record"]["failure"] = "projection-invalid" + self.assertEqual(repaired_twin, accepted) + def test_drift_is_recomputed_from_the_two_recorded_bundles(self) -> None: left = json.loads((FIXTURE / "left-bundle.json").read_text(encoding="utf-8")) right = json.loads((FIXTURE / "right-bundle.json").read_text(encoding="utf-8")) From 33f11ddb077777c51d59c115070b3474e21c5f07 Mon Sep 17 00:00:00 2001 From: kikashy Date: Thu, 22 Oct 2026 15:08:33 -0400 Subject: [PATCH 2/3] Add the later-sibling admission vectors and report named by the build (#213) Signed-off-by: kikashy --- ...later-sibling-admission-wrong-failure.json | 4 ++ ...later-sibling-admission-wrong-failure.json | 44 +++++++++++++++++++ .../vectors/later-sibling-admission.json | 43 ++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 conformance/workflow/reports/later-sibling-admission-wrong-failure.json create mode 100644 conformance/workflow/vectors/later-sibling-admission-wrong-failure.json create mode 100644 conformance/workflow/vectors/later-sibling-admission.json diff --git a/conformance/workflow/reports/later-sibling-admission-wrong-failure.json b/conformance/workflow/reports/later-sibling-admission-wrong-failure.json new file mode 100644 index 0000000..e23a637 --- /dev/null +++ b/conformance/workflow/reports/later-sibling-admission-wrong-failure.json @@ -0,0 +1,4 @@ +{ + "result": "finding", + "finding": "inventory-mismatch" +} diff --git a/conformance/workflow/vectors/later-sibling-admission-wrong-failure.json b/conformance/workflow/vectors/later-sibling-admission-wrong-failure.json new file mode 100644 index 0000000..c6ced32 --- /dev/null +++ b/conformance/workflow/vectors/later-sibling-admission-wrong-failure.json @@ -0,0 +1,44 @@ +{ + "vectorVersion": 1, + "kind": "later-sibling-admission", + "stage": "people", + "maxRecords": 2, + "instances": [ + { + "id": "admitted-first", + "admission": "admitted", + "requestedOutputs": 1, + "record": { + "caseDigest": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + "state": "completed", "failure": null, "attempts": 1 + } + }, + { + "id": "bound-trigger", + "admission": "admitted", + "requestedOutputs": 2, + "record": { + "caseDigest": "sha256:2222222222222222222222222222222222222222222222222222222222222222", + "state": "failed", "failure": "limit-exceeded", "attempts": 1 + } + }, + { + "id": "projection-failed", + "admission": "projection-invalid", + "requestedOutputs": 0, + "record": { + "caseDigest": null, "state": "failed", "failure": "limit-exceeded", "attempts": 0 + } + }, + { + "id": "admitted-later", + "admission": "admitted", + "requestedOutputs": 0, + "record": { + "caseDigest": "sha256:4444444444444444444444444444444444444444444444444444444444444444", + "state": "failed", "failure": "limit-exceeded", "attempts": 0 + } + } + ], + "expectedFinding": "inventory-mismatch" +} diff --git a/conformance/workflow/vectors/later-sibling-admission.json b/conformance/workflow/vectors/later-sibling-admission.json new file mode 100644 index 0000000..d24d5fe --- /dev/null +++ b/conformance/workflow/vectors/later-sibling-admission.json @@ -0,0 +1,43 @@ +{ + "vectorVersion": 1, + "kind": "later-sibling-admission", + "stage": "people", + "maxRecords": 2, + "instances": [ + { + "id": "admitted-first", + "admission": "admitted", + "requestedOutputs": 1, + "record": { + "caseDigest": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + "state": "completed", "failure": null, "attempts": 1 + } + }, + { + "id": "bound-trigger", + "admission": "admitted", + "requestedOutputs": 2, + "record": { + "caseDigest": "sha256:2222222222222222222222222222222222222222222222222222222222222222", + "state": "failed", "failure": "limit-exceeded", "attempts": 1 + } + }, + { + "id": "projection-failed", + "admission": "projection-invalid", + "requestedOutputs": 0, + "record": { + "caseDigest": null, "state": "failed", "failure": "projection-invalid", "attempts": 0 + } + }, + { + "id": "admitted-later", + "admission": "admitted", + "requestedOutputs": 0, + "record": { + "caseDigest": "sha256:4444444444444444444444444444444444444444444444444444444444444444", + "state": "failed", "failure": "limit-exceeded", "attempts": 0 + } + } + ] +} From 7cdba7565c27d1f739362fb52ed878f1120da895 Mon Sep 17 00:00:00 2001 From: kikashy Date: Thu, 22 Oct 2026 15:12:49 -0400 Subject: [PATCH 3/3] State in the verifier's check 4 that the later-sibling rule follows admission (#213 review) Signed-off-by: kikashy --- rfcs/0020-workflow.md | 4 +++- spec/workflow.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/rfcs/0020-workflow.md b/rfcs/0020-workflow.md index 963c9e6..7eb1694 100644 --- a/rfcs/0020-workflow.md +++ b/rfcs/0020-workflow.md @@ -741,7 +741,9 @@ check 3, an earlier instance's `influence-mismatch` precedes a later one's `case (`case-mismatch`), then inherited classes, influence and explicit admission (`influence-mismatch`). 4. The expected instance inventory, admission keys, attempts, terminal states, selection refusals, - deterministic bound accounting and stage closure (`inventory-mismatch`); then re-extract all + deterministic bound accounting and stage closure (`inventory-mismatch`), applying §2's + later-sibling rule only after each instance's admission status is established and only to + admitted instances, so an instance refused at admission keeps its own code; then re-extract all outputs, compare payload bytes, order, complete sets, parent/selection references, relation and lineage pins (`output-mismatch`). Neither failure nor missing data becomes `[]`. 5. From those checked output records and their retained dispositions, recompute every stage diff --git a/spec/workflow.md b/spec/workflow.md index 43ad3f5..3267fb5 100644 --- a/spec/workflow.md +++ b/spec/workflow.md @@ -563,7 +563,9 @@ check 3, an earlier instance's `influence-mismatch` precedes a later one's `case (`case-mismatch`), then inherited classes, influence and explicit admission (`influence-mismatch`). 4. The expected instance inventory, admission keys, attempts, terminal states, selection refusals, - deterministic bound accounting and stage closure (`inventory-mismatch`); then re-extract all + deterministic bound accounting and stage closure (`inventory-mismatch`), applying §2's + later-sibling rule only after each instance's admission status is established and only to + admitted instances, so an instance refused at admission keeps its own code; then re-extract all outputs, compare payload bytes, order, complete sets, parent/selection references, relation and lineage pins (`output-mismatch`). Neither failure nor missing data becomes `[]`. 5. From those checked output records and their retained dispositions, recompute every stage