diff --git a/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T154810Z-000-cc50c6f5fc75.json b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T154810Z-000-cc50c6f5fc75.json new file mode 100644 index 00000000..382b2dce --- /dev/null +++ b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T154810Z-000-cc50c6f5fc75.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T15:48:10Z", + "actor": "human:timo", + "artifact": "story:bounded-batch-and-facade-reads", + "kind": "story", + "revision": 11, + "change": { + "change": "evidence", + "kind": "verification", + "source": "Release baseline shared-clock probe failed its 2x bound: medians at55/601/1203 events 92.517484ms/7.038153041s/12.449723852s; all fixture shape/history/revision assertions passed; exit101", + "reference": "crates/entity-eventlog/tests/shared_clock_cost.rs" + } +} diff --git a/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T154941Z-000-1ebf2e311ef1.json b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T154941Z-000-1ebf2e311ef1.json new file mode 100644 index 00000000..59214b0f --- /dev/null +++ b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T154941Z-000-1ebf2e311ef1.json @@ -0,0 +1,14 @@ +{ + "at": "2026-10-03T15:49:41Z", + "actor": "human:timo", + "artifact": "story:bounded-batch-and-facade-reads", + "kind": "story", + "revision": 11, + "change": { + "change": "evidence", + "kind": "review_outcome", + "source": "review-result:issue-51-continuity-design", + "review": "review-result:issue-51-continuity-design", + "outcome": "fixed" + } +} diff --git a/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T161907Z-000-1d88be876420.json b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T161907Z-000-1d88be876420.json new file mode 100644 index 00000000..2beaf33a --- /dev/null +++ b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T161907Z-000-1d88be876420.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T16:19:07Z", + "actor": "human:timo", + "artifact": "story:bounded-batch-and-facade-reads", + "kind": "story", + "revision": 13, + "change": { + "change": "evidence", + "kind": "metric_observation", + "source": "Release shared-clock public-facade probe; exact55/601/1203events; baselineexit101 medians92.517484/7038.153041/12449.723852ms; treatmentexit0 medians10.511861/11.983571/17.701283ms; growth1.140/1.684x; unchanged capture/decode/model counters unchanged", + "reference": "crates/entity-eventlog/tests/shared_clock_cost.rs" + } +} diff --git a/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T162046Z-000-edcaa652a57d.json b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T162046Z-000-edcaa652a57d.json new file mode 100644 index 00000000..3ec325e4 --- /dev/null +++ b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T162046Z-000-edcaa652a57d.json @@ -0,0 +1,14 @@ +{ + "at": "2026-10-03T16:20:46Z", + "actor": "human:timo", + "artifact": "story:bounded-batch-and-facade-reads", + "kind": "story", + "revision": 13, + "change": { + "change": "evidence", + "kind": "review_outcome", + "source": "review-result:issue-51-runtime-adversary", + "review": "review-result:issue-51-runtime-adversary", + "outcome": "fixed" + } +} diff --git a/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T162046Z-001-1a19901c891a.json b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T162046Z-001-1a19901c891a.json new file mode 100644 index 00000000..f752b3d2 --- /dev/null +++ b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T162046Z-001-1a19901c891a.json @@ -0,0 +1,14 @@ +{ + "at": "2026-10-03T16:20:46Z", + "actor": "human:timo", + "artifact": "story:bounded-batch-and-facade-reads", + "kind": "story", + "revision": 13, + "change": { + "change": "evidence", + "kind": "review_outcome", + "source": "review-result:issue-51-runtime-recheck", + "review": "review-result:issue-51-runtime-recheck", + "outcome": "no-op" + } +} diff --git a/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T162930Z-000-346c7a5cf85c.json b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T162930Z-000-346c7a5cf85c.json new file mode 100644 index 00000000..46829a7e --- /dev/null +++ b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T162930Z-000-346c7a5cf85c.json @@ -0,0 +1,14 @@ +{ + "at": "2026-10-03T16:29:30Z", + "actor": "human:timo", + "artifact": "story:bounded-batch-and-facade-reads", + "kind": "story", + "revision": 16, + "change": { + "change": "evidence", + "kind": "review_outcome", + "source": "review-result:issue-51-provider-checker", + "review": "review-result:issue-51-provider-checker", + "outcome": "no-op" + } +} diff --git a/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163512Z-000-cb38cd06e892.json b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163512Z-000-cb38cd06e892.json new file mode 100644 index 00000000..1abb27bb --- /dev/null +++ b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163512Z-000-cb38cd06e892.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T16:35:12Z", + "actor": "human:timo", + "artifact": "story:bounded-batch-and-facade-reads", + "kind": "story", + "revision": 16, + "change": { + "change": "evidence", + "kind": "ess_conformance_coverage_v1", + "source": "{\"completed_at\":\"1791045312963\",\"conformance_status\":\"passed\",\"counts\":{\"error\":0,\"failed\":0,\"passed\":421,\"skipped\":0,\"total\":421,\"unsupported\":0},\"coverage\":{\"counts\":{\"authored\":310,\"generated\":111,\"outside\":0,\"refused\":0},\"knowledge\":\"complete_inventory\",\"refused\":[],\"selection\":{\"filter\":{\"kind\":\"all\"},\"origins\":\"generated_and_authored\",\"scope\":{\"kind\":\"system\"}}},\"execution_status\":\"passed\",\"format\":\"ess-conformance-report/2\",\"implementation\":\"entity-runtime source-sha256:50edd1c86b576122b432d4d9d820d818712531ce4fa167d020bbf44ba83ee7e2;executable-sha256:4c3b992f177f575479d259e50e4a6e5feba647a2aaf0aa84a86a3acc0ae08ab3\",\"input_transport\":\"wrapped_raw_suite\",\"policy\":\"complete-selection/1\",\"producer_profile\":\"rust-scenario-status/1\",\"report_input\":\"target/ess-conformance/report.json\",\"selected_ids\":[\"entity.core.Address/outcome/returned\",\"entity.core.CompareNumbers/outcome/returned\",\"entity.core.Continue/outcome/returned\",\"entity.core.Create/outcome/returned\",\"entity.core.CreateDerived/outcome/returned\",\"entity.core.Execute/outcome/returned\",\"entity.core.Fulfill/outcome/returned\",\"entity.core.NormalizeArguments/outcome/returned\",\"entity.core.Prepare/outcome/returned\",\"entity.core.Register/outcome/returned\",\"entity.core.Registry/outcome/returned\",\"entity.core.Rehydrate/outcome/returned\",\"entity.core.Replace/outcome/returned\",\"entity.core.Replay/outcome/returned\",\"entity.core.ReplayReturned/outcome/returned\",\"entity.core.ReplayTampered/outcome/returned\",\"entity.core.RuntimeCreate/outcome/returned\",\"entity.core.RuntimeExecute/outcome/returned\",\"entity.core.Timestamp/outcome/returned\",\"entity.core.ValidateDefinition/outcome/returned\",\"entity.core.ValidateRegistry/outcome/returned\",\"entity.core/authored/additional-top-level-and-nested-fields-are-explicit\",\"entity.core/authored/all-scalar-and-collection-kinds-validate\",\"entity.core/authored/argument-validation-precedes-transition-refusal\",\"entity.core/authored/assignments-read-one-pre-operation-map\",\"entity.core/authored/blank-identity-is-a-typed-validation-refusal\",\"entity.core/authored/condition-documents-have-one-known-nonempty-operator\",\"entity.core/authored/conditional-presence-registration-requires-one-typed-optional-leaf\",\"entity.core/authored/conditional-presence-requires-service2\",\"entity.core/authored/constraint-kind-is-checked\",\"entity.core/authored/constraints-accumulate-independent-value-errors\",\"entity.core/authored/continuation-keeps-implicit-paths-refusals-and-fulfillment-boundary\",\"entity.core/authored/creation-accumulates-field-errors\",\"entity.core/authored/creation-applies-defaults-and-returns-an-exact-event\",\"entity.core/authored/creation-template-has-no-previous-state\",\"entity.core/authored/default-version-multiple-transition-sources-and-object-arguments\",\"entity.core/authored/defaults-must-satisfy-schema\",\"entity.core/authored/definition-keys-are-closed\",\"entity.core/authored/definition-validation-accumulates-independent-defects\",\"entity.core/authored/derived-and-supplied-creation-share-literal-record-bytes\",\"entity.core/authored/derived-create-requires-logical-identity\",\"entity.core/authored/derived-creation-refuses-circular-assignment\",\"entity.core/authored/derived-creation-refuses-circular-selector\",\"entity.core/authored/derived-creation-selects-first-identity\",\"entity.core/authored/derived-creation-selects-second-identity\",\"entity.core/authored/derived-identity-uses-logical-value\",\"entity.core/authored/deterministic-kernel-record-bytes\",\"entity.core/authored/duplicate-events-retain-declared-order-and-multiplicity\",\"entity.core/authored/entity-mismatch-precedes-unknown-state\",\"entity.core/authored/exact-number-35\",\"entity.core/authored/exact-number-36\",\"entity.core/authored/exact-number-37\",\"entity.core/authored/exact-number-38\",\"entity.core/authored/exact-number-39\",\"entity.core/authored/exact-number-40\",\"entity.core/authored/exact-number-41\",\"entity.core/authored/failed-precondition-produces-no-decision\",\"entity.core/authored/field-input-must-be-an-object\",\"entity.core/authored/fulfillment-declaration-boundary-service1\",\"entity.core/authored/fulfillment-declaration-boundary-service2\",\"entity.core/authored/fulfillment-declaration-boundary-service3\",\"entity.core/authored/fulfillment-registration-closes-placement-identity-and-action-shapes\",\"entity.core/authored/fulfillment-request-follows-subject-state-and-preconditions\",\"entity.core/authored/identity-address-42\",\"entity.core/authored/identity-address-43\",\"entity.core/authored/identity-address-44\",\"entity.core/authored/identity-address-45\",\"entity.core/authored/identity-address-46\",\"entity.core/authored/identity-address-47\",\"entity.core/authored/identity-rejects-untyped-and-invalid-public-address-values\",\"entity.core/authored/invariant-cannot-read-arguments\",\"entity.core/authored/invariants-judge-the-post-operation-fields\",\"entity.core/authored/kernel-condition-after\",\"entity.core/authored/kernel-condition-all\",\"entity.core/authored/kernel-condition-any\",\"entity.core/authored/kernel-condition-before\",\"entity.core/authored/kernel-condition-contains\",\"entity.core/authored/kernel-condition-eq\",\"entity.core/authored/kernel-condition-exists\",\"entity.core/authored/kernel-condition-gt\",\"entity.core/authored/kernel-condition-gte\",\"entity.core/authored/kernel-condition-in\",\"entity.core/authored/kernel-condition-lt\",\"entity.core/authored/kernel-condition-lte\",\"entity.core/authored/kernel-condition-missing\",\"entity.core/authored/kernel-condition-ne\",\"entity.core/authored/kernel-condition-not\",\"entity.core/authored/kernel-condition-prefix\",\"entity.core/authored/kernel-condition-prefix-case-sensitive\",\"entity.core/authored/kernel-condition-suffix\",\"entity.core/authored/kernel-rejects-service-only-declaration-families\",\"entity.core/authored/legacy-event-fold-checks-every-recorded-operation-fact\",\"entity.core/authored/legacy-event-rehydration-refuses-service1\",\"entity.core/authored/legacy-event-rehydration-refuses-service2\",\"entity.core/authored/legacy-event-rehydration-refuses-service3\",\"entity.core/authored/legacy-event-rehydration-rejects-changed-payload\",\"entity.core/authored/legacy-event-rehydration-rejects-missing-duplicate\",\"entity.core/authored/legacy-event-rehydration-rejects-revision-gap\",\"entity.core/authored/legacy-event-rehydration-retains-duplicate-events\",\"entity.core/authored/logical-identity-must-match-storage-address\",\"entity.core/authored/missing-and-null-references-differ-from-literal-null\",\"entity.core/authored/nested-defaults-apply-only-to-supplied-objects\",\"entity.core/authored/nested-quantifier-binders-shadow-only-their-inner-scope\",\"entity.core/authored/nested-reference-must-be-declared\",\"entity.core/authored/normalization-applies-argument-defaults\",\"entity.core/authored/numeric-adjacent-integers-past-binary64\",\"entity.core/authored/numeric-kernel-exact-door\",\"entity.core/authored/numeric-two-wire-doors\",\"entity.core/authored/numeric-underflow-remains-stored\",\"entity.core/authored/numeric-wire-and-literal-doors\",\"entity.core/authored/operation-cannot-change-identity-mirror\",\"entity.core/authored/owns-carriers-are-required-on-the-target\",\"entity.core/authored/post-set-fields-are-revalidated\",\"entity.core/authored/precondition-cannot-read-next-state\",\"entity.core/authored/preload-payment-negative\",\"entity.core/authored/preload-payment-positive\",\"entity.core/authored/preload-payment-zero\",\"entity.core/authored/preload-quantifiers-preserve-vacuity-and-dominating-truth\",\"entity.core/authored/preload-validates-input-before-refusal\",\"entity.core/authored/prepared-continuation-binds-subject-and-matches-direct-result\",\"entity.core/authored/projection-references-and-state-are-validated\",\"entity.core/authored/references-carrier-kind-comes-from-target-identity\",\"entity.core/authored/references-require-a-target-declaration-and-nonblank-value\",\"entity.core/authored/registration-lifecycle-defects-do-not-cascade\",\"entity.core/authored/registry-duplicate-and-invalid-replacement-preserve-definition\",\"entity.core/authored/registry-mutual-nested-and-argument-references-resolve-together\",\"entity.core/authored/registry-refuses-two-owners-of-one-entity\",\"entity.core/authored/registry-relations-check-carrier-on-target\",\"entity.core/authored/registry-resolves-reference-targets-as-a-set\",\"entity.core/authored/rehydrate-rejects-empty-history\",\"entity.core/authored/relation-carrier-optionality-and-single-claim-are-enforced\",\"entity.core/authored/replay-rejects-empty-history\",\"entity.core/authored/replay-rejects-fields-tampering\",\"entity.core/authored/replay-rejects-identity-tampering\",\"entity.core/authored/replay-rejects-missing-definition-tampering\",\"entity.core/authored/replay-rejects-revision-tampering\",\"entity.core/authored/replay-rejects-state-tampering\",\"entity.core/authored/replay-zero-event-genesis\",\"entity.core/authored/returned-kernel1-decisions-replay-and-reject-tampering\",\"entity.core/authored/returned-service1-decisions-replay-and-reject-tampering\",\"entity.core/authored/returned-service2-decisions-replay-and-reject-tampering\",\"entity.core/authored/returned-service3-decisions-replay-and-reject-tampering\",\"entity.core/authored/revision-exhaustion-refuses-before-effects\",\"entity.core/authored/rule-refusals-retain-names-and-default-messages\",\"entity.core/authored/runtime-executes-the-exact-instance-definition\",\"entity.core/authored/runtime-refuses-missing-registry-version\",\"entity.core/authored/schema-constraints-refuse-every-inapplicable-family\",\"entity.core/authored/service-map-and-union-declarations-and-outer-values-are-closed\",\"entity.core/authored/service-outcome-names-defaults-and-observability\",\"entity.core/authored/service-quantifiers-validate-binders-collections-operands-and-depth\",\"entity.core/authored/service-responses-must-be-declared-and-complete\",\"entity.core/authored/service-wrong-state-and-effect-declarations-are-closed\",\"entity.core/authored/service1-adjacent-union-preserves-tag-and-value\",\"entity.core/authored/service1-array-ordinal\",\"entity.core/authored/service1-bare-state-guard-is-a-selector\",\"entity.core/authored/service1-binary64-refuses-nonfinite-magnitude\",\"entity.core/authored/service1-binary64-retains-negative-zero\",\"entity.core/authored/service1-collection-count\",\"entity.core/authored/service1-compare-missing\",\"entity.core/authored/service1-compare-number\",\"entity.core/authored/service1-compare-ranked-text\",\"entity.core/authored/service1-first-matching-outcome-wins\",\"entity.core/authored/service1-forall-empty\",\"entity.core/authored/service1-forall-false\",\"entity.core/authored/service1-forall-true\",\"entity.core/authored/service1-forany-empty\",\"entity.core/authored/service1-forany-missing\",\"entity.core/authored/service1-forany-true\",\"entity.core/authored/service1-map-count-not-count-key\",\"entity.core/authored/service1-map-member-errors-retain-paths\",\"entity.core/authored/service1-map-quantified-values\",\"entity.core/authored/service1-map-values-are-typed\",\"entity.core/authored/service1-no-matching-outcome-is-typed\",\"entity.core/authored/service1-rank-disagreement\",\"entity.core/authored/service1-rank-without-scale\",\"entity.core/authored/service1-selection-fallback\",\"entity.core/authored/service1-selection-fast\",\"entity.core/authored/service1-selection-terminal\",\"entity.core/authored/service1-selection-wrong-source\",\"entity.core/authored/service1-state-guard-skips-unobservable-input\",\"entity.core/authored/service1-truthy-missing\",\"entity.core/authored/service1-truthy-true\",\"entity.core/authored/service1-truthy-zero\",\"entity.core/authored/service1-union-refuses-unknown-tag\",\"entity.core/authored/service1-unknown-negation\",\"entity.core/authored/service2-presence-absent\",\"entity.core/authored/service2-presence-null\",\"entity.core/authored/service2-presence-present\",\"entity.core/authored/service2-presence-rejects-null-string\",\"entity.core/authored/service3-direct-execute-requires-fulfillment\",\"entity.core/authored/service3-fulfillment-extra-coordinate\",\"entity.core/authored/service3-fulfillment-invalid-value\",\"entity.core/authored/service3-fulfillment-missing-coordinate\",\"entity.core/authored/service3-fulfillment-preserve\",\"entity.core/authored/service3-fulfillment-remove-required\",\"entity.core/authored/service3-fulfillment-set-remove\",\"entity.core/authored/service3-refusal-needs-no-loaded-instance\",\"entity.core/authored/silent-execution-advances-once-with-no-events\",\"entity.core/authored/templates-recurse-escape-dollar-and-read-post-set-fields\",\"entity.core/authored/timestamp-48\",\"entity.core/authored/timestamp-49\",\"entity.core/authored/timestamp-50\",\"entity.core/authored/timestamp-unknown-diagnostics-and-literal-validation\",\"entity.core/authored/transition-refusal-precedes-failed-precondition\",\"entity.core/authored/unknown-operation-is-typed\",\"entity.core/authored/unknown-rules-do-not-become-false\",\"entity.core/authored/unknown-state-precedes-unknown-operation\",\"entity.core/authored/unobservable-rules-report-sorted-unique-addresses\",\"entity.core/authored/unresolved-json-template-path-is-an-error\",\"entity.executor.Batch/outcome/returned\",\"entity.executor.CancelBatch/outcome/returned\",\"entity.executor.CapturedAppend/outcome/returned\",\"entity.executor.CapturedMerge/outcome/returned\",\"entity.executor.ClearRegistry/outcome/returned\",\"entity.executor.ClearTrace/outcome/returned\",\"entity.executor.Create/outcome/returned\",\"entity.executor.Execute/outcome/returned\",\"entity.executor.History/outcome/returned\",\"entity.executor.HistoryCoordinates/outcome/returned\",\"entity.executor.Load/outcome/returned\",\"entity.executor.LookupReceipt/outcome/returned\",\"entity.executor.LookupRecord/outcome/returned\",\"entity.executor.Observe/outcome/returned\",\"entity.executor.RecordingRefusals/outcome/returned\",\"entity.executor.RefusalReasons/outcome/returned\",\"entity.executor.Refusals/outcome/returned\",\"entity.executor.Register/outcome/returned\",\"entity.executor.ScriptAppend/outcome/returned\",\"entity.executor.SeedImported/outcome/returned\",\"entity.executor.SupplyHistory/outcome/returned\",\"entity.executor.SupplyLoadFailure/outcome/returned\",\"entity.executor.Tamper/outcome/returned\",\"entity.executor.Trace/outcome/returned\",\"entity.executor.VerifyComplete/outcome/returned\",\"entity.executor/authored/changed-batch-order-conflicts\",\"entity.executor/authored/changed-request-conflicts\",\"entity.executor/authored/complete-decisions-retain-duplicate-events\",\"entity.executor/authored/corrupt-history-never-recovers-success\",\"entity.executor/authored/dropped-pending-response-is-not-rollback\",\"entity.executor/authored/dropping-before-poll-dispatches-nothing\",\"entity.executor/authored/duplicate-identities-refuse-before-io\",\"entity.executor/authored/empty-batches-perform-no-io\",\"entity.executor/authored/empty-history-is-not-a-merge\",\"entity.executor/authored/executor-input-refusal-existing-create\",\"entity.executor/authored/executor-input-refusal-missing-execute\",\"entity.executor/authored/global-record-identities-span-subjects-and-kinds\",\"entity.executor/authored/historical-observation-has-no-committed-receipt\",\"entity.executor/authored/imported-exact-retry-is-historical\",\"entity.executor/authored/imported-missing-definition-cannot-prove-retry\",\"entity.executor/authored/issue-50-batch-refusal-is-atomic\",\"entity.executor/authored/issue-50-malformed-version\",\"entity.executor/authored/issue-50-versioned-merge-preserves-proof\",\"entity.executor/authored/kernel-refusal-is-recorded\",\"entity.executor/authored/malformed-create-is-rejected-before-io\",\"entity.executor/authored/merge-appends-over-observation-tips\",\"entity.executor/authored/merge-candidate-uses-first-state-and-all-tips\",\"entity.executor/authored/merge-checks-head-and-highest-revision\",\"entity.executor/authored/mixed-batch-orders-decisions-and-observations\",\"entity.executor/authored/named-and-single-retries-keep-original-receipts\",\"entity.executor/authored/optional-refusal-recorder-sees-conflicts-not-malformed-input\",\"entity.executor/authored/ordinary-forked-write-records-refusal\",\"entity.executor/authored/refusal-ignores-success-fulfillments\",\"entity.executor/authored/saved-definition-retry-precedes-current-authority\",\"entity.executor/authored/service-1-saved-definition-retry\",\"entity.executor/authored/service-2-conditional-presence-retry\",\"entity.executor/authored/service-3-fulfillment-retry\",\"entity.executor/authored/single-key-must-match-member\",\"entity.executor/authored/stale-observation-rolls-back-whole-batch\",\"entity.executor/authored/subject-refusal-ignores-success-fulfillments\",\"entity.executor/authored/uncertain-response-recovers-one-original-receipt\",\"entity.executor/authored/unreachable-is-never-absent\",\"entity.query.Continue/outcome/returned\",\"entity.query.DecodeCursor/outcome/returned\",\"entity.query.Limit/outcome/returned\",\"entity.query.Ordered/outcome/returned\",\"entity.query.Page/outcome/returned\",\"entity.query.Query/outcome/returned\",\"entity.query.Seed/outcome/returned\",\"entity.query/authored/consumed-descending-identity-refuses\",\"entity.query/authored/consumed-duplicate-identity-refuses\",\"entity.query/authored/consumed-foreign-entity-refuses\",\"entity.query/authored/consumed-prefix-before-cursor-is-validated\",\"entity.query/authored/fixed-dataset-complete-traversal\",\"entity.query/authored/foreign-entity-cursor-refuses\",\"entity.query/authored/foreign-predicate-cursor-refuses\",\"entity.query/authored/malformed-cursors-are-typed-refusals\",\"entity.query/authored/matching-pages-skip-nonmatches\",\"entity.query/authored/nested-numeric-containment\",\"entity.query/authored/numeric-values-remain-exact\",\"entity.query/authored/ordered-api-stops-after-matching-lookahead\",\"entity.query/authored/page-builder-truncates-matches\",\"entity.query/authored/page-limit-boundaries\",\"entity.query/authored/query-document-keys-are-closed\",\"entity.query/authored/recursive-containment\",\"entity.shell.Calls/outcome/returned\",\"entity.shell.ClearCalls/outcome/returned\",\"entity.shell.ClearRegistry/outcome/returned\",\"entity.shell.Create/outcome/returned\",\"entity.shell.Events/outcome/returned\",\"entity.shell.Execute/outcome/returned\",\"entity.shell.Fault/outcome/returned\",\"entity.shell.Get/outcome/returned\",\"entity.shell.List/outcome/returned\",\"entity.shell.Open/outcome/returned\",\"entity.shell.Race/outcome/returned\",\"entity.shell.Records/outcome/returned\",\"entity.shell.Register/outcome/returned\",\"entity.shell.Reopen/outcome/returned\",\"entity.shell.Replace/outcome/returned\",\"entity.shell/authored/file-changed-retry-conflicts\",\"entity.shell/authored/file-create-retry-after-advancement\",\"entity.shell/authored/file-invalid-recording-publishes-nothing\",\"entity.shell/authored/file-missing-subjects\",\"entity.shell/authored/file-provider-failures-preserve-classification\",\"entity.shell/authored/file-retry-uses-original-record\",\"entity.shell/authored/file-revision-rechecked-at-commit\",\"entity.shell/authored/file-round-trip\",\"entity.shell/authored/file-stale-intent-precedes-kernel\",\"entity.shell/authored/kernel-refusal-precedes-recording-validation\",\"entity.shell/authored/memory-changed-retry-conflicts\",\"entity.shell/authored/memory-create-retry-after-advancement\",\"entity.shell/authored/memory-invalid-recording-publishes-nothing\",\"entity.shell/authored/memory-missing-subjects\",\"entity.shell/authored/memory-retry-uses-original-record\",\"entity.shell/authored/memory-revision-rechecked-at-commit\",\"entity.shell/authored/memory-round-trip\",\"entity.shell/authored/memory-stale-intent-precedes-kernel\",\"entity.shell/authored/provider-failures-preserve-classification\",\"entity.shell/authored/retry-does-not-hide-read-failure\",\"entity.store.AcquireLegacy/outcome/returned\",\"entity.store.Append/outcome/returned\",\"entity.store.AtomicBatch/outcome/returned\",\"entity.store.BatchCoordinate/outcome/returned\",\"entity.store.BranchHeads/outcome/returned\",\"entity.store.BranchTips/outcome/returned\",\"entity.store.CanonicalBytes/outcome/returned\",\"entity.store.CheckExpectation/outcome/returned\",\"entity.store.ClearTrace/outcome/returned\",\"entity.store.Commit/outcome/returned\",\"entity.store.CommitRecorded/outcome/returned\",\"entity.store.Events/outcome/returned\",\"entity.store.FileExists/outcome/returned\",\"entity.store.History/outcome/returned\",\"entity.store.Ids/outcome/returned\",\"entity.store.Load/outcome/returned\",\"entity.store.LookupBatch/outcome/returned\",\"entity.store.LookupRecord/outcome/returned\",\"entity.store.MemberCoordinate/outcome/returned\",\"entity.store.MigrateFile/outcome/returned\",\"entity.store.Observations/outcome/returned\",\"entity.store.Observe/outcome/returned\",\"entity.store.Project/outcome/returned\",\"entity.store.ReadFileFixture/outcome/returned\",\"entity.store.ReadRecordDomain/outcome/returned\",\"entity.store.RecordDomains/outcome/returned\",\"entity.store.RecordFraming/outcome/returned\",\"entity.store.Records/outcome/returned\",\"entity.store.Reopen/outcome/returned\",\"entity.store.RequestBytes/outcome/returned\",\"entity.store.Seal/outcome/returned\",\"entity.store.SeedImported/outcome/returned\",\"entity.store.SetPosition/outcome/returned\",\"entity.store.SubjectCoordinate/outcome/returned\",\"entity.store.Tamper/outcome/returned\",\"entity.store.Trace/outcome/returned\",\"entity.store.ValidateEnvelope/outcome/returned\",\"entity.store.ValidateObservation/outcome/returned\",\"entity.store.ValidateRecordedCommit/outcome/returned\",\"entity.store.VerifyComplete/outcome/returned\",\"entity.store.VerifyExplicit/outcome/returned\",\"entity.store.VerifySubject/outcome/returned\",\"entity.store.WriteFileFixture/outcome/returned\",\"entity.store/authored/an-absent-memory-subject-is-none\",\"entity.store/authored/canonical-byte-encoding-preserves-exact-number-tokens\",\"entity.store/authored/complete-assurance-requires-provider-capture\",\"entity.store/authored/coordinates-retain-opaque-subjects-and-disjoint-batch-namespaces\",\"entity.store/authored/envelope-null-provenance-is-explicit\",\"entity.store/authored/expectation-absent-is-distinct-from-revision-zero\",\"entity.store/authored/file-absence\",\"entity.store/authored/file-corrupt-marker-is-not-absence\",\"entity.store/authored/file-exact-retry-after-state-advances\",\"entity.store/authored/file-global-record-identity\",\"entity.store/authored/file-invalid-recording-is-inert\",\"entity.store/authored/file-listed-identities-are-sorted\",\"entity.store/authored/file-observations-do-not-advance\",\"entity.store/authored/file-ordered-duplicate-events\",\"entity.store/authored/file-reopen\",\"entity.store/authored/file-stale-write-preserves-state\",\"entity.store/authored/imported-boundary-retains-limited-assurance\",\"entity.store/authored/imported-evidence-cannot-exceed-anchor\",\"entity.store/authored/legacy-acquisition-of-absent-source-is-read-only\",\"entity.store/authored/legacy-acquisition-preserves-only-known-order\",\"entity.store/authored/lineage-decisions-produce-two-heads\",\"entity.store/authored/memory-absence\",\"entity.store/authored/memory-atomic-order\",\"entity.store/authored/memory-atomic-rollback\",\"entity.store/authored/memory-empty-batch\",\"entity.store/authored/memory-exact-retry-after-state-advances\",\"entity.store/authored/memory-global-record-identity\",\"entity.store/authored/memory-invalid-recording-is-inert\",\"entity.store/authored/memory-listed-identities-are-sorted\",\"entity.store/authored/memory-observations-do-not-advance\",\"entity.store/authored/memory-ordered-duplicate-events\",\"entity.store/authored/memory-stale-write-preserves-state\",\"entity.store/authored/migration-dry-run-and-out-of-place\",\"entity.store/authored/migration-invalid-source-publishes-nothing\",\"entity.store/authored/observation-revisions-are-positive-signed-range\",\"entity.store/authored/observations-are-tips-but-not-decision-heads\",\"entity.store/authored/older-framing-reader-refuses-before-payload\",\"entity.store/authored/projection-groups-sorted-identities\",\"entity.store/authored/projection-scalar-and-nested-keys\",\"entity.store/authored/record-domain-kernel-1\",\"entity.store/authored/record-domain-service-1\",\"entity.store/authored/record-domain-service-2\",\"entity.store/authored/record-domain-service-3\",\"entity.store/authored/recorded-commit-result-must-match-instance\",\"entity.store/authored/recorded-memory-atomic-observation-order\",\"entity.store/authored/recorded-memory-atomic-rollback\",\"entity.store/authored/recorded-memory-writer-revalidates-public-members\",\"entity.store/authored/recorded-position-overflow-is-atomic\",\"entity.store/authored/tampered-complete-history-is-rejected\"],\"selection\":{\"filter\":{\"kind\":\"all\"},\"origins\":\"generated_and_authored\",\"scope\":{\"kind\":\"system\"}},\"spec_digest\":\"a13e6c07be82c913363ea52b8042c6e379343acfda7fcc03a212c0942569da5d\",\"specification\":\"entity/v1\",\"suite\":{\"digest\":\"sha256:53c11c53dab9e23d4b05f25cc8b5c3a6da92572d3a2b53ba2f394bd0735dd9e6\",\"digest_profile\":\"sha256-json-bytes/1\",\"version\":\"ess-conformance/29\"},\"suite_input\":\"ess/generated/suite.json\"}", + "reference": "target/ess-conformance/report.json" + } +} diff --git a/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163543Z-000-34c2ea656f91.json b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163543Z-000-34c2ea656f91.json new file mode 100644 index 00000000..9429e78f --- /dev/null +++ b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163543Z-000-34c2ea656f91.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T16:35:43Z", + "actor": "human:timo", + "artifact": "story:bounded-batch-and-facade-reads", + "kind": "story", + "revision": 16, + "change": { + "change": "evidence", + "kind": "ess_conformance_coverage_v1", + "source": "{\"completed_at\":\"1791045343826\",\"conformance_status\":\"passed\",\"counts\":{\"error\":0,\"failed\":0,\"passed\":17,\"skipped\":0,\"total\":17,\"unsupported\":0},\"coverage\":{\"counts\":{\"authored\":7,\"generated\":10,\"outside\":0,\"refused\":0},\"knowledge\":\"complete_inventory\",\"refused\":[],\"selection\":{\"filter\":{\"kind\":\"all\"},\"origins\":\"generated_and_authored\",\"scope\":{\"kind\":\"system\"}}},\"execution_status\":\"passed\",\"format\":\"ess-conformance-report/2\",\"implementation\":\"entity-runtime source-sha256:50edd1c86b576122b432d4d9d820d818712531ce4fa167d020bbf44ba83ee7e2;executable-sha256:a5e300d731bf76d39d7b322ad766f7142cc3afd5ce1a64701c88829841067ceb\",\"input_transport\":\"wrapped_raw_suite\",\"policy\":\"complete-selection/1\",\"producer_profile\":\"rust-scenario-status/1\",\"report_input\":\"target/provider-ess-conformance/report.json\",\"selected_ids\":[\"entity-provider.tracking.Batch/outcome/returned\",\"entity-provider.tracking.Create/outcome/returned\",\"entity-provider.tracking.Histories/outcome/returned\",\"entity-provider.tracking.Load/outcome/returned\",\"entity-provider.tracking.LookupBatch/outcome/returned\",\"entity-provider.tracking.Provision/outcome/returned\",\"entity-provider.tracking.Register/outcome/returned\",\"entity-provider.tracking.Reopen/outcome/returned\",\"entity-provider.tracking.Snapshot/outcome/returned\",\"entity-provider.tracking.SqlMutate/outcome/returned\",\"entity-provider.tracking/authored/multi-subject-histories-one-scope\",\"entity-provider.tracking/authored/unchanged-capture-retains-verified-state\",\"entity-provider.tracking/authored/unchanged-head-blob-tamper\",\"entity-provider.tracking/authored/unchanged-head-delete-blob-tamper\",\"entity-provider.tracking/authored/unchanged-head-event-tamper\",\"entity-provider.tracking/authored/unchanged-head-identity-tamper\",\"entity-provider.tracking/authored/unchanged-head-projection-tamper\"],\"selection\":{\"filter\":{\"kind\":\"all\"},\"origins\":\"generated_and_authored\",\"scope\":{\"kind\":\"system\"}},\"spec_digest\":\"ef6eb41f4dac9a7d51a5c0dc675933062d24096f195456a1dafe88ff8de672ac\",\"specification\":\"entity-provider/v1\",\"suite\":{\"digest\":\"sha256:7ed494f22f60f90f908dcc1050a915c2c8de1551a582f028b05e12aa879ebc99\",\"digest_profile\":\"sha256-json-bytes/1\",\"version\":\"ess-conformance/29\"},\"suite_input\":\"ess/provider-tracking/generated/suite.json\"}", + "reference": "target/provider-ess-conformance/report.json" + } +} diff --git a/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163823Z-000-96a42c1043a4.json b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163823Z-000-96a42c1043a4.json new file mode 100644 index 00000000..2fa2a174 --- /dev/null +++ b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163823Z-000-96a42c1043a4.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T16:38:23Z", + "actor": "human:timo", + "artifact": "story:bounded-batch-and-facade-reads", + "kind": "story", + "revision": 16, + "change": { + "change": "evidence", + "kind": "test_result", + "source": "Final task check exit 0; every ordered step completed; real PostgreSQL lane executed; all-feature runtime 186 passed; performance probe separately executed and passed", + "reference": "docs/ess/evidence/provider-tracking/README.md" + } +} diff --git a/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163823Z-001-d89858ded513.json b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163823Z-001-d89858ded513.json new file mode 100644 index 00000000..7f33ee06 --- /dev/null +++ b/.engineering/evidence/story/bounded-batch-and-facade-reads/20261003T163823Z-001-d89858ded513.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T16:38:23Z", + "actor": "human:timo", + "artifact": "story:bounded-batch-and-facade-reads", + "kind": "story", + "revision": 16, + "change": { + "change": "evidence", + "kind": "metric_observation", + "source": "Published-pin release probe exit 0, one test; medians 11.532148/13.049370/13.568977 ms at 55/601/1203 events, ratios 1.132/1.177 under unchanged 2x assertion", + "reference": "docs/ess/evidence/provider-tracking/performance-final.txt" + } +} diff --git a/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T150254Z-000-e7a4c6b71c63.json b/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T150254Z-000-e7a4c6b71c63.json new file mode 100644 index 00000000..1e0eedd4 --- /dev/null +++ b/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T150254Z-000-e7a4c6b71c63.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T15:02:54Z", + "actor": "human:timo", + "artifact": "story:declared-refusal-before-fulfillment-validation", + "kind": "story", + "revision": 6, + "change": { + "change": "evidence", + "kind": "verification", + "source": "cargo test -p entity-executor --locked: exact stored-field refusal failed before change and passed after; independent batch/retry controls pass", + "reference": "review-result:issue-49-adversary-public" + } +} diff --git a/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T150254Z-001-06cb651715cc.json b/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T150254Z-001-06cb651715cc.json new file mode 100644 index 00000000..908a5831 --- /dev/null +++ b/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T150254Z-001-06cb651715cc.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T15:02:54Z", + "actor": "human:timo", + "artifact": "story:declared-refusal-before-fulfillment-validation", + "kind": "story", + "revision": 6, + "change": { + "change": "evidence", + "kind": "test_result", + "source": "cargo test -p entity-executor --locked: 22 passed, 0 failed; fmt and clippy exited 0", + "reference": "c6164443df9f303f4898f1de1abfc1c899e8c921" + } +} diff --git a/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T150846Z-000-1063e2524428.json b/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T150846Z-000-1063e2524428.json new file mode 100644 index 00000000..4d45bba8 --- /dev/null +++ b/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T150846Z-000-1063e2524428.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T15:08:46Z", + "actor": "human:timo", + "artifact": "story:declared-refusal-before-fulfillment-validation", + "kind": "story", + "revision": 6, + "change": { + "change": "evidence", + "kind": "ess_conformance_coverage_v1", + "source": "{\"completed_at\":\"1791040126321\",\"conformance_status\":\"passed\",\"counts\":{\"error\":0,\"failed\":0,\"passed\":416,\"skipped\":0,\"total\":416,\"unsupported\":0},\"coverage\":{\"counts\":{\"authored\":305,\"generated\":111,\"outside\":0,\"refused\":0},\"knowledge\":\"complete_inventory\",\"refused\":[],\"selection\":{\"filter\":{\"kind\":\"all\"},\"origins\":\"generated_and_authored\",\"scope\":{\"kind\":\"system\"}}},\"execution_status\":\"passed\",\"format\":\"ess-conformance-report/2\",\"implementation\":\"entity-runtime source-sha256:202ac320bc3b6d0479034a23e1c3bd1e7b52b6d62a2f42c23d4201b0d2974f6e;executable-sha256:9264ce31c0787d0ed2f58c373cebef196bac1bae7b43e292ad3d3003b78a7b29\",\"input_transport\":\"wrapped_raw_suite\",\"policy\":\"complete-selection/1\",\"producer_profile\":\"rust-scenario-status/1\",\"report_input\":\"target/issue-wave-scratch/issue49-conformance/report.json\",\"selected_ids\":[\"entity.core.Address/outcome/returned\",\"entity.core.CompareNumbers/outcome/returned\",\"entity.core.Continue/outcome/returned\",\"entity.core.Create/outcome/returned\",\"entity.core.CreateDerived/outcome/returned\",\"entity.core.Execute/outcome/returned\",\"entity.core.Fulfill/outcome/returned\",\"entity.core.NormalizeArguments/outcome/returned\",\"entity.core.Prepare/outcome/returned\",\"entity.core.Register/outcome/returned\",\"entity.core.Registry/outcome/returned\",\"entity.core.Rehydrate/outcome/returned\",\"entity.core.Replace/outcome/returned\",\"entity.core.Replay/outcome/returned\",\"entity.core.ReplayReturned/outcome/returned\",\"entity.core.ReplayTampered/outcome/returned\",\"entity.core.RuntimeCreate/outcome/returned\",\"entity.core.RuntimeExecute/outcome/returned\",\"entity.core.Timestamp/outcome/returned\",\"entity.core.ValidateDefinition/outcome/returned\",\"entity.core.ValidateRegistry/outcome/returned\",\"entity.core/authored/additional-top-level-and-nested-fields-are-explicit\",\"entity.core/authored/all-scalar-and-collection-kinds-validate\",\"entity.core/authored/argument-validation-precedes-transition-refusal\",\"entity.core/authored/assignments-read-one-pre-operation-map\",\"entity.core/authored/blank-identity-is-a-typed-validation-refusal\",\"entity.core/authored/condition-documents-have-one-known-nonempty-operator\",\"entity.core/authored/conditional-presence-registration-requires-one-typed-optional-leaf\",\"entity.core/authored/conditional-presence-requires-service2\",\"entity.core/authored/constraint-kind-is-checked\",\"entity.core/authored/constraints-accumulate-independent-value-errors\",\"entity.core/authored/continuation-keeps-implicit-paths-refusals-and-fulfillment-boundary\",\"entity.core/authored/creation-accumulates-field-errors\",\"entity.core/authored/creation-applies-defaults-and-returns-an-exact-event\",\"entity.core/authored/creation-template-has-no-previous-state\",\"entity.core/authored/default-version-multiple-transition-sources-and-object-arguments\",\"entity.core/authored/defaults-must-satisfy-schema\",\"entity.core/authored/definition-keys-are-closed\",\"entity.core/authored/definition-validation-accumulates-independent-defects\",\"entity.core/authored/derived-and-supplied-creation-share-literal-record-bytes\",\"entity.core/authored/derived-create-requires-logical-identity\",\"entity.core/authored/derived-creation-refuses-circular-assignment\",\"entity.core/authored/derived-creation-refuses-circular-selector\",\"entity.core/authored/derived-creation-selects-first-identity\",\"entity.core/authored/derived-creation-selects-second-identity\",\"entity.core/authored/derived-identity-uses-logical-value\",\"entity.core/authored/deterministic-kernel-record-bytes\",\"entity.core/authored/duplicate-events-retain-declared-order-and-multiplicity\",\"entity.core/authored/entity-mismatch-precedes-unknown-state\",\"entity.core/authored/exact-number-35\",\"entity.core/authored/exact-number-36\",\"entity.core/authored/exact-number-37\",\"entity.core/authored/exact-number-38\",\"entity.core/authored/exact-number-39\",\"entity.core/authored/exact-number-40\",\"entity.core/authored/exact-number-41\",\"entity.core/authored/failed-precondition-produces-no-decision\",\"entity.core/authored/field-input-must-be-an-object\",\"entity.core/authored/fulfillment-declaration-boundary-service1\",\"entity.core/authored/fulfillment-declaration-boundary-service2\",\"entity.core/authored/fulfillment-declaration-boundary-service3\",\"entity.core/authored/fulfillment-registration-closes-placement-identity-and-action-shapes\",\"entity.core/authored/fulfillment-request-follows-subject-state-and-preconditions\",\"entity.core/authored/identity-address-42\",\"entity.core/authored/identity-address-43\",\"entity.core/authored/identity-address-44\",\"entity.core/authored/identity-address-45\",\"entity.core/authored/identity-address-46\",\"entity.core/authored/identity-address-47\",\"entity.core/authored/identity-rejects-untyped-and-invalid-public-address-values\",\"entity.core/authored/invariant-cannot-read-arguments\",\"entity.core/authored/invariants-judge-the-post-operation-fields\",\"entity.core/authored/kernel-condition-after\",\"entity.core/authored/kernel-condition-all\",\"entity.core/authored/kernel-condition-any\",\"entity.core/authored/kernel-condition-before\",\"entity.core/authored/kernel-condition-contains\",\"entity.core/authored/kernel-condition-eq\",\"entity.core/authored/kernel-condition-exists\",\"entity.core/authored/kernel-condition-gt\",\"entity.core/authored/kernel-condition-gte\",\"entity.core/authored/kernel-condition-in\",\"entity.core/authored/kernel-condition-lt\",\"entity.core/authored/kernel-condition-lte\",\"entity.core/authored/kernel-condition-missing\",\"entity.core/authored/kernel-condition-ne\",\"entity.core/authored/kernel-condition-not\",\"entity.core/authored/kernel-condition-prefix\",\"entity.core/authored/kernel-condition-prefix-case-sensitive\",\"entity.core/authored/kernel-condition-suffix\",\"entity.core/authored/kernel-rejects-service-only-declaration-families\",\"entity.core/authored/legacy-event-fold-checks-every-recorded-operation-fact\",\"entity.core/authored/legacy-event-rehydration-refuses-service1\",\"entity.core/authored/legacy-event-rehydration-refuses-service2\",\"entity.core/authored/legacy-event-rehydration-refuses-service3\",\"entity.core/authored/legacy-event-rehydration-rejects-changed-payload\",\"entity.core/authored/legacy-event-rehydration-rejects-missing-duplicate\",\"entity.core/authored/legacy-event-rehydration-rejects-revision-gap\",\"entity.core/authored/legacy-event-rehydration-retains-duplicate-events\",\"entity.core/authored/logical-identity-must-match-storage-address\",\"entity.core/authored/missing-and-null-references-differ-from-literal-null\",\"entity.core/authored/nested-defaults-apply-only-to-supplied-objects\",\"entity.core/authored/nested-quantifier-binders-shadow-only-their-inner-scope\",\"entity.core/authored/nested-reference-must-be-declared\",\"entity.core/authored/normalization-applies-argument-defaults\",\"entity.core/authored/numeric-adjacent-integers-past-binary64\",\"entity.core/authored/numeric-kernel-exact-door\",\"entity.core/authored/numeric-two-wire-doors\",\"entity.core/authored/numeric-underflow-remains-stored\",\"entity.core/authored/numeric-wire-and-literal-doors\",\"entity.core/authored/operation-cannot-change-identity-mirror\",\"entity.core/authored/owns-carriers-are-required-on-the-target\",\"entity.core/authored/post-set-fields-are-revalidated\",\"entity.core/authored/precondition-cannot-read-next-state\",\"entity.core/authored/preload-payment-negative\",\"entity.core/authored/preload-payment-positive\",\"entity.core/authored/preload-payment-zero\",\"entity.core/authored/preload-quantifiers-preserve-vacuity-and-dominating-truth\",\"entity.core/authored/preload-validates-input-before-refusal\",\"entity.core/authored/prepared-continuation-binds-subject-and-matches-direct-result\",\"entity.core/authored/projection-references-and-state-are-validated\",\"entity.core/authored/references-carrier-kind-comes-from-target-identity\",\"entity.core/authored/references-require-a-target-declaration-and-nonblank-value\",\"entity.core/authored/registration-lifecycle-defects-do-not-cascade\",\"entity.core/authored/registry-duplicate-and-invalid-replacement-preserve-definition\",\"entity.core/authored/registry-mutual-nested-and-argument-references-resolve-together\",\"entity.core/authored/registry-refuses-two-owners-of-one-entity\",\"entity.core/authored/registry-relations-check-carrier-on-target\",\"entity.core/authored/registry-resolves-reference-targets-as-a-set\",\"entity.core/authored/rehydrate-rejects-empty-history\",\"entity.core/authored/relation-carrier-optionality-and-single-claim-are-enforced\",\"entity.core/authored/replay-rejects-empty-history\",\"entity.core/authored/replay-rejects-fields-tampering\",\"entity.core/authored/replay-rejects-identity-tampering\",\"entity.core/authored/replay-rejects-missing-definition-tampering\",\"entity.core/authored/replay-rejects-revision-tampering\",\"entity.core/authored/replay-rejects-state-tampering\",\"entity.core/authored/replay-zero-event-genesis\",\"entity.core/authored/returned-kernel1-decisions-replay-and-reject-tampering\",\"entity.core/authored/returned-service1-decisions-replay-and-reject-tampering\",\"entity.core/authored/returned-service2-decisions-replay-and-reject-tampering\",\"entity.core/authored/returned-service3-decisions-replay-and-reject-tampering\",\"entity.core/authored/revision-exhaustion-refuses-before-effects\",\"entity.core/authored/rule-refusals-retain-names-and-default-messages\",\"entity.core/authored/runtime-executes-the-exact-instance-definition\",\"entity.core/authored/runtime-refuses-missing-registry-version\",\"entity.core/authored/schema-constraints-refuse-every-inapplicable-family\",\"entity.core/authored/service-map-and-union-declarations-and-outer-values-are-closed\",\"entity.core/authored/service-outcome-names-defaults-and-observability\",\"entity.core/authored/service-quantifiers-validate-binders-collections-operands-and-depth\",\"entity.core/authored/service-responses-must-be-declared-and-complete\",\"entity.core/authored/service-wrong-state-and-effect-declarations-are-closed\",\"entity.core/authored/service1-adjacent-union-preserves-tag-and-value\",\"entity.core/authored/service1-array-ordinal\",\"entity.core/authored/service1-bare-state-guard-is-a-selector\",\"entity.core/authored/service1-binary64-refuses-nonfinite-magnitude\",\"entity.core/authored/service1-binary64-retains-negative-zero\",\"entity.core/authored/service1-collection-count\",\"entity.core/authored/service1-compare-missing\",\"entity.core/authored/service1-compare-number\",\"entity.core/authored/service1-compare-ranked-text\",\"entity.core/authored/service1-first-matching-outcome-wins\",\"entity.core/authored/service1-forall-empty\",\"entity.core/authored/service1-forall-false\",\"entity.core/authored/service1-forall-true\",\"entity.core/authored/service1-forany-empty\",\"entity.core/authored/service1-forany-missing\",\"entity.core/authored/service1-forany-true\",\"entity.core/authored/service1-map-count-not-count-key\",\"entity.core/authored/service1-map-member-errors-retain-paths\",\"entity.core/authored/service1-map-quantified-values\",\"entity.core/authored/service1-map-values-are-typed\",\"entity.core/authored/service1-no-matching-outcome-is-typed\",\"entity.core/authored/service1-rank-disagreement\",\"entity.core/authored/service1-rank-without-scale\",\"entity.core/authored/service1-selection-fallback\",\"entity.core/authored/service1-selection-fast\",\"entity.core/authored/service1-selection-terminal\",\"entity.core/authored/service1-selection-wrong-source\",\"entity.core/authored/service1-state-guard-skips-unobservable-input\",\"entity.core/authored/service1-truthy-missing\",\"entity.core/authored/service1-truthy-true\",\"entity.core/authored/service1-truthy-zero\",\"entity.core/authored/service1-union-refuses-unknown-tag\",\"entity.core/authored/service1-unknown-negation\",\"entity.core/authored/service2-presence-absent\",\"entity.core/authored/service2-presence-null\",\"entity.core/authored/service2-presence-present\",\"entity.core/authored/service2-presence-rejects-null-string\",\"entity.core/authored/service3-direct-execute-requires-fulfillment\",\"entity.core/authored/service3-fulfillment-extra-coordinate\",\"entity.core/authored/service3-fulfillment-invalid-value\",\"entity.core/authored/service3-fulfillment-missing-coordinate\",\"entity.core/authored/service3-fulfillment-preserve\",\"entity.core/authored/service3-fulfillment-remove-required\",\"entity.core/authored/service3-fulfillment-set-remove\",\"entity.core/authored/service3-refusal-needs-no-loaded-instance\",\"entity.core/authored/silent-execution-advances-once-with-no-events\",\"entity.core/authored/templates-recurse-escape-dollar-and-read-post-set-fields\",\"entity.core/authored/timestamp-48\",\"entity.core/authored/timestamp-49\",\"entity.core/authored/timestamp-50\",\"entity.core/authored/timestamp-unknown-diagnostics-and-literal-validation\",\"entity.core/authored/transition-refusal-precedes-failed-precondition\",\"entity.core/authored/unknown-operation-is-typed\",\"entity.core/authored/unknown-rules-do-not-become-false\",\"entity.core/authored/unknown-state-precedes-unknown-operation\",\"entity.core/authored/unobservable-rules-report-sorted-unique-addresses\",\"entity.core/authored/unresolved-json-template-path-is-an-error\",\"entity.executor.Batch/outcome/returned\",\"entity.executor.CancelBatch/outcome/returned\",\"entity.executor.CapturedAppend/outcome/returned\",\"entity.executor.CapturedMerge/outcome/returned\",\"entity.executor.ClearRegistry/outcome/returned\",\"entity.executor.ClearTrace/outcome/returned\",\"entity.executor.Create/outcome/returned\",\"entity.executor.Execute/outcome/returned\",\"entity.executor.History/outcome/returned\",\"entity.executor.HistoryCoordinates/outcome/returned\",\"entity.executor.Load/outcome/returned\",\"entity.executor.LookupReceipt/outcome/returned\",\"entity.executor.LookupRecord/outcome/returned\",\"entity.executor.Observe/outcome/returned\",\"entity.executor.RecordingRefusals/outcome/returned\",\"entity.executor.RefusalReasons/outcome/returned\",\"entity.executor.Refusals/outcome/returned\",\"entity.executor.Register/outcome/returned\",\"entity.executor.ScriptAppend/outcome/returned\",\"entity.executor.SeedImported/outcome/returned\",\"entity.executor.SupplyHistory/outcome/returned\",\"entity.executor.SupplyLoadFailure/outcome/returned\",\"entity.executor.Tamper/outcome/returned\",\"entity.executor.Trace/outcome/returned\",\"entity.executor.VerifyComplete/outcome/returned\",\"entity.executor/authored/changed-batch-order-conflicts\",\"entity.executor/authored/changed-request-conflicts\",\"entity.executor/authored/complete-decisions-retain-duplicate-events\",\"entity.executor/authored/corrupt-history-never-recovers-success\",\"entity.executor/authored/dropped-pending-response-is-not-rollback\",\"entity.executor/authored/dropping-before-poll-dispatches-nothing\",\"entity.executor/authored/duplicate-identities-refuse-before-io\",\"entity.executor/authored/empty-batches-perform-no-io\",\"entity.executor/authored/empty-history-is-not-a-merge\",\"entity.executor/authored/global-record-identities-span-subjects-and-kinds\",\"entity.executor/authored/historical-observation-has-no-committed-receipt\",\"entity.executor/authored/imported-exact-retry-is-historical\",\"entity.executor/authored/imported-missing-definition-cannot-prove-retry\",\"entity.executor/authored/kernel-refusal-is-recorded\",\"entity.executor/authored/malformed-create-is-rejected-before-io\",\"entity.executor/authored/merge-appends-over-observation-tips\",\"entity.executor/authored/merge-candidate-uses-first-state-and-all-tips\",\"entity.executor/authored/merge-checks-head-and-highest-revision\",\"entity.executor/authored/mixed-batch-orders-decisions-and-observations\",\"entity.executor/authored/named-and-single-retries-keep-original-receipts\",\"entity.executor/authored/optional-refusal-recorder-sees-conflicts-not-malformed-input\",\"entity.executor/authored/ordinary-forked-write-records-refusal\",\"entity.executor/authored/refusal-ignores-success-fulfillments\",\"entity.executor/authored/saved-definition-retry-precedes-current-authority\",\"entity.executor/authored/service-1-saved-definition-retry\",\"entity.executor/authored/service-2-conditional-presence-retry\",\"entity.executor/authored/service-3-fulfillment-retry\",\"entity.executor/authored/single-key-must-match-member\",\"entity.executor/authored/stale-observation-rolls-back-whole-batch\",\"entity.executor/authored/subject-refusal-ignores-success-fulfillments\",\"entity.executor/authored/uncertain-response-recovers-one-original-receipt\",\"entity.executor/authored/unreachable-is-never-absent\",\"entity.query.Continue/outcome/returned\",\"entity.query.DecodeCursor/outcome/returned\",\"entity.query.Limit/outcome/returned\",\"entity.query.Ordered/outcome/returned\",\"entity.query.Page/outcome/returned\",\"entity.query.Query/outcome/returned\",\"entity.query.Seed/outcome/returned\",\"entity.query/authored/consumed-descending-identity-refuses\",\"entity.query/authored/consumed-duplicate-identity-refuses\",\"entity.query/authored/consumed-foreign-entity-refuses\",\"entity.query/authored/consumed-prefix-before-cursor-is-validated\",\"entity.query/authored/fixed-dataset-complete-traversal\",\"entity.query/authored/foreign-entity-cursor-refuses\",\"entity.query/authored/foreign-predicate-cursor-refuses\",\"entity.query/authored/malformed-cursors-are-typed-refusals\",\"entity.query/authored/matching-pages-skip-nonmatches\",\"entity.query/authored/nested-numeric-containment\",\"entity.query/authored/numeric-values-remain-exact\",\"entity.query/authored/ordered-api-stops-after-matching-lookahead\",\"entity.query/authored/page-builder-truncates-matches\",\"entity.query/authored/page-limit-boundaries\",\"entity.query/authored/query-document-keys-are-closed\",\"entity.query/authored/recursive-containment\",\"entity.shell.Calls/outcome/returned\",\"entity.shell.ClearCalls/outcome/returned\",\"entity.shell.ClearRegistry/outcome/returned\",\"entity.shell.Create/outcome/returned\",\"entity.shell.Events/outcome/returned\",\"entity.shell.Execute/outcome/returned\",\"entity.shell.Fault/outcome/returned\",\"entity.shell.Get/outcome/returned\",\"entity.shell.List/outcome/returned\",\"entity.shell.Open/outcome/returned\",\"entity.shell.Race/outcome/returned\",\"entity.shell.Records/outcome/returned\",\"entity.shell.Register/outcome/returned\",\"entity.shell.Reopen/outcome/returned\",\"entity.shell.Replace/outcome/returned\",\"entity.shell/authored/file-changed-retry-conflicts\",\"entity.shell/authored/file-create-retry-after-advancement\",\"entity.shell/authored/file-invalid-recording-publishes-nothing\",\"entity.shell/authored/file-missing-subjects\",\"entity.shell/authored/file-provider-failures-preserve-classification\",\"entity.shell/authored/file-retry-uses-original-record\",\"entity.shell/authored/file-revision-rechecked-at-commit\",\"entity.shell/authored/file-round-trip\",\"entity.shell/authored/file-stale-intent-precedes-kernel\",\"entity.shell/authored/kernel-refusal-precedes-recording-validation\",\"entity.shell/authored/memory-changed-retry-conflicts\",\"entity.shell/authored/memory-create-retry-after-advancement\",\"entity.shell/authored/memory-invalid-recording-publishes-nothing\",\"entity.shell/authored/memory-missing-subjects\",\"entity.shell/authored/memory-retry-uses-original-record\",\"entity.shell/authored/memory-revision-rechecked-at-commit\",\"entity.shell/authored/memory-round-trip\",\"entity.shell/authored/memory-stale-intent-precedes-kernel\",\"entity.shell/authored/provider-failures-preserve-classification\",\"entity.shell/authored/retry-does-not-hide-read-failure\",\"entity.store.AcquireLegacy/outcome/returned\",\"entity.store.Append/outcome/returned\",\"entity.store.AtomicBatch/outcome/returned\",\"entity.store.BatchCoordinate/outcome/returned\",\"entity.store.BranchHeads/outcome/returned\",\"entity.store.BranchTips/outcome/returned\",\"entity.store.CanonicalBytes/outcome/returned\",\"entity.store.CheckExpectation/outcome/returned\",\"entity.store.ClearTrace/outcome/returned\",\"entity.store.Commit/outcome/returned\",\"entity.store.CommitRecorded/outcome/returned\",\"entity.store.Events/outcome/returned\",\"entity.store.FileExists/outcome/returned\",\"entity.store.History/outcome/returned\",\"entity.store.Ids/outcome/returned\",\"entity.store.Load/outcome/returned\",\"entity.store.LookupBatch/outcome/returned\",\"entity.store.LookupRecord/outcome/returned\",\"entity.store.MemberCoordinate/outcome/returned\",\"entity.store.MigrateFile/outcome/returned\",\"entity.store.Observations/outcome/returned\",\"entity.store.Observe/outcome/returned\",\"entity.store.Project/outcome/returned\",\"entity.store.ReadFileFixture/outcome/returned\",\"entity.store.ReadRecordDomain/outcome/returned\",\"entity.store.RecordDomains/outcome/returned\",\"entity.store.RecordFraming/outcome/returned\",\"entity.store.Records/outcome/returned\",\"entity.store.Reopen/outcome/returned\",\"entity.store.RequestBytes/outcome/returned\",\"entity.store.Seal/outcome/returned\",\"entity.store.SeedImported/outcome/returned\",\"entity.store.SetPosition/outcome/returned\",\"entity.store.SubjectCoordinate/outcome/returned\",\"entity.store.Tamper/outcome/returned\",\"entity.store.Trace/outcome/returned\",\"entity.store.ValidateEnvelope/outcome/returned\",\"entity.store.ValidateObservation/outcome/returned\",\"entity.store.ValidateRecordedCommit/outcome/returned\",\"entity.store.VerifyComplete/outcome/returned\",\"entity.store.VerifyExplicit/outcome/returned\",\"entity.store.VerifySubject/outcome/returned\",\"entity.store.WriteFileFixture/outcome/returned\",\"entity.store/authored/an-absent-memory-subject-is-none\",\"entity.store/authored/canonical-byte-encoding-preserves-exact-number-tokens\",\"entity.store/authored/complete-assurance-requires-provider-capture\",\"entity.store/authored/coordinates-retain-opaque-subjects-and-disjoint-batch-namespaces\",\"entity.store/authored/envelope-null-provenance-is-explicit\",\"entity.store/authored/expectation-absent-is-distinct-from-revision-zero\",\"entity.store/authored/file-absence\",\"entity.store/authored/file-corrupt-marker-is-not-absence\",\"entity.store/authored/file-exact-retry-after-state-advances\",\"entity.store/authored/file-global-record-identity\",\"entity.store/authored/file-invalid-recording-is-inert\",\"entity.store/authored/file-listed-identities-are-sorted\",\"entity.store/authored/file-observations-do-not-advance\",\"entity.store/authored/file-ordered-duplicate-events\",\"entity.store/authored/file-reopen\",\"entity.store/authored/file-stale-write-preserves-state\",\"entity.store/authored/imported-boundary-retains-limited-assurance\",\"entity.store/authored/imported-evidence-cannot-exceed-anchor\",\"entity.store/authored/legacy-acquisition-of-absent-source-is-read-only\",\"entity.store/authored/legacy-acquisition-preserves-only-known-order\",\"entity.store/authored/lineage-decisions-produce-two-heads\",\"entity.store/authored/memory-absence\",\"entity.store/authored/memory-atomic-order\",\"entity.store/authored/memory-atomic-rollback\",\"entity.store/authored/memory-empty-batch\",\"entity.store/authored/memory-exact-retry-after-state-advances\",\"entity.store/authored/memory-global-record-identity\",\"entity.store/authored/memory-invalid-recording-is-inert\",\"entity.store/authored/memory-listed-identities-are-sorted\",\"entity.store/authored/memory-observations-do-not-advance\",\"entity.store/authored/memory-ordered-duplicate-events\",\"entity.store/authored/memory-stale-write-preserves-state\",\"entity.store/authored/migration-dry-run-and-out-of-place\",\"entity.store/authored/migration-invalid-source-publishes-nothing\",\"entity.store/authored/observation-revisions-are-positive-signed-range\",\"entity.store/authored/observations-are-tips-but-not-decision-heads\",\"entity.store/authored/older-framing-reader-refuses-before-payload\",\"entity.store/authored/projection-groups-sorted-identities\",\"entity.store/authored/projection-scalar-and-nested-keys\",\"entity.store/authored/record-domain-kernel-1\",\"entity.store/authored/record-domain-service-1\",\"entity.store/authored/record-domain-service-2\",\"entity.store/authored/record-domain-service-3\",\"entity.store/authored/recorded-commit-result-must-match-instance\",\"entity.store/authored/recorded-memory-atomic-observation-order\",\"entity.store/authored/recorded-memory-atomic-rollback\",\"entity.store/authored/recorded-memory-writer-revalidates-public-members\",\"entity.store/authored/recorded-position-overflow-is-atomic\",\"entity.store/authored/tampered-complete-history-is-rejected\"],\"selection\":{\"filter\":{\"kind\":\"all\"},\"origins\":\"generated_and_authored\",\"scope\":{\"kind\":\"system\"}},\"spec_digest\":\"a13e6c07be82c913363ea52b8042c6e379343acfda7fcc03a212c0942569da5d\",\"specification\":\"entity/v1\",\"suite\":{\"digest\":\"sha256:5ed54aedfc920900dafdd77dbdd2264fd4225afaf89d70ff7e7599fa106c03ea\",\"digest_profile\":\"sha256-json-bytes/1\",\"version\":\"ess-conformance/29\"},\"suite_input\":\"target/issue-wave-scratch/issue49-conformance/suite.json\"}", + "reference": "target/issue-wave-scratch/issue49-conformance/report.json" + } +} diff --git a/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T152840Z-000-157048cf525d.json b/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T152840Z-000-157048cf525d.json new file mode 100644 index 00000000..5a27e059 --- /dev/null +++ b/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T152840Z-000-157048cf525d.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T15:28:40Z", + "actor": "human:timo", + "artifact": "story:declared-refusal-before-fulfillment-validation", + "kind": "story", + "revision": 12, + "change": { + "change": "evidence", + "kind": "test_result", + "source": "CARGO_PROFILE_DEV_DEBUG=0 CARGO_BUILD_JOBS=2 task check exited 0 on integrated #49 and #50; PostgreSQL lane explicitly skipped because ENTITY_POSTGRES_URL unset", + "reference": "target/issue-wave-scratch/task-check.log" + } +} diff --git a/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T163923Z-000-f121bcda1025.json b/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T163923Z-000-f121bcda1025.json new file mode 100644 index 00000000..eac27055 --- /dev/null +++ b/.engineering/evidence/story/declared-refusal-before-fulfillment-validation/20261003T163923Z-000-f121bcda1025.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T16:39:23Z", + "actor": "human:timo", + "artifact": "story:declared-refusal-before-fulfillment-validation", + "kind": "story", + "revision": 13, + "change": { + "change": "evidence", + "kind": "test_result", + "source": "Final all-issue integration task check exit 0 with 421 original ESS scenarios passing and real PostgreSQL execution", + "reference": "docs/ess/evidence/provider-tracking/README.md" + } +} diff --git a/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152143Z-000-a73e97ba66fa.json b/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152143Z-000-a73e97ba66fa.json new file mode 100644 index 00000000..76198a4c --- /dev/null +++ b/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152143Z-000-a73e97ba66fa.json @@ -0,0 +1,14 @@ +{ + "at": "2026-10-03T15:21:43Z", + "actor": "human:timo", + "artifact": "story:executor-input-refusal-before-existence", + "kind": "story", + "revision": 7, + "change": { + "change": "evidence", + "kind": "review_outcome", + "source": "review-result:issue-50-adversary", + "review": "review-result:issue-50-adversary", + "outcome": "no-op" + } +} diff --git a/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152242Z-000-84da457b2fd2.json b/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152242Z-000-84da457b2fd2.json new file mode 100644 index 00000000..81ed6949 --- /dev/null +++ b/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152242Z-000-84da457b2fd2.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T15:22:42Z", + "actor": "human:timo", + "artifact": "story:executor-input-refusal-before-existence", + "kind": "story", + "revision": 12, + "change": { + "change": "evidence", + "kind": "ess_conformance_coverage_v1", + "source": "{\"completed_at\":\"1791040962123\",\"conformance_status\":\"passed\",\"counts\":{\"error\":0,\"failed\":0,\"passed\":421,\"skipped\":0,\"total\":421,\"unsupported\":0},\"coverage\":{\"counts\":{\"authored\":310,\"generated\":111,\"outside\":0,\"refused\":0},\"knowledge\":\"complete_inventory\",\"refused\":[],\"selection\":{\"filter\":{\"kind\":\"all\"},\"origins\":\"generated_and_authored\",\"scope\":{\"kind\":\"system\"}}},\"execution_status\":\"passed\",\"format\":\"ess-conformance-report/2\",\"implementation\":\"entity-runtime source-sha256:59c9cf0fd7c97fa88732d3d0a4ff06219ca7e3194290debb48ebca8129c39955;executable-sha256:1cf94802afa433d57aff230b120373005d33e852ec9de8c7f6c18ce7a3ddbefb\",\"input_transport\":\"wrapped_raw_suite\",\"policy\":\"complete-selection/1\",\"producer_profile\":\"rust-scenario-status/1\",\"report_input\":\"target/issue-wave-scratch/issue50-conformance/report.json\",\"selected_ids\":[\"entity.core.Address/outcome/returned\",\"entity.core.CompareNumbers/outcome/returned\",\"entity.core.Continue/outcome/returned\",\"entity.core.Create/outcome/returned\",\"entity.core.CreateDerived/outcome/returned\",\"entity.core.Execute/outcome/returned\",\"entity.core.Fulfill/outcome/returned\",\"entity.core.NormalizeArguments/outcome/returned\",\"entity.core.Prepare/outcome/returned\",\"entity.core.Register/outcome/returned\",\"entity.core.Registry/outcome/returned\",\"entity.core.Rehydrate/outcome/returned\",\"entity.core.Replace/outcome/returned\",\"entity.core.Replay/outcome/returned\",\"entity.core.ReplayReturned/outcome/returned\",\"entity.core.ReplayTampered/outcome/returned\",\"entity.core.RuntimeCreate/outcome/returned\",\"entity.core.RuntimeExecute/outcome/returned\",\"entity.core.Timestamp/outcome/returned\",\"entity.core.ValidateDefinition/outcome/returned\",\"entity.core.ValidateRegistry/outcome/returned\",\"entity.core/authored/additional-top-level-and-nested-fields-are-explicit\",\"entity.core/authored/all-scalar-and-collection-kinds-validate\",\"entity.core/authored/argument-validation-precedes-transition-refusal\",\"entity.core/authored/assignments-read-one-pre-operation-map\",\"entity.core/authored/blank-identity-is-a-typed-validation-refusal\",\"entity.core/authored/condition-documents-have-one-known-nonempty-operator\",\"entity.core/authored/conditional-presence-registration-requires-one-typed-optional-leaf\",\"entity.core/authored/conditional-presence-requires-service2\",\"entity.core/authored/constraint-kind-is-checked\",\"entity.core/authored/constraints-accumulate-independent-value-errors\",\"entity.core/authored/continuation-keeps-implicit-paths-refusals-and-fulfillment-boundary\",\"entity.core/authored/creation-accumulates-field-errors\",\"entity.core/authored/creation-applies-defaults-and-returns-an-exact-event\",\"entity.core/authored/creation-template-has-no-previous-state\",\"entity.core/authored/default-version-multiple-transition-sources-and-object-arguments\",\"entity.core/authored/defaults-must-satisfy-schema\",\"entity.core/authored/definition-keys-are-closed\",\"entity.core/authored/definition-validation-accumulates-independent-defects\",\"entity.core/authored/derived-and-supplied-creation-share-literal-record-bytes\",\"entity.core/authored/derived-create-requires-logical-identity\",\"entity.core/authored/derived-creation-refuses-circular-assignment\",\"entity.core/authored/derived-creation-refuses-circular-selector\",\"entity.core/authored/derived-creation-selects-first-identity\",\"entity.core/authored/derived-creation-selects-second-identity\",\"entity.core/authored/derived-identity-uses-logical-value\",\"entity.core/authored/deterministic-kernel-record-bytes\",\"entity.core/authored/duplicate-events-retain-declared-order-and-multiplicity\",\"entity.core/authored/entity-mismatch-precedes-unknown-state\",\"entity.core/authored/exact-number-35\",\"entity.core/authored/exact-number-36\",\"entity.core/authored/exact-number-37\",\"entity.core/authored/exact-number-38\",\"entity.core/authored/exact-number-39\",\"entity.core/authored/exact-number-40\",\"entity.core/authored/exact-number-41\",\"entity.core/authored/failed-precondition-produces-no-decision\",\"entity.core/authored/field-input-must-be-an-object\",\"entity.core/authored/fulfillment-declaration-boundary-service1\",\"entity.core/authored/fulfillment-declaration-boundary-service2\",\"entity.core/authored/fulfillment-declaration-boundary-service3\",\"entity.core/authored/fulfillment-registration-closes-placement-identity-and-action-shapes\",\"entity.core/authored/fulfillment-request-follows-subject-state-and-preconditions\",\"entity.core/authored/identity-address-42\",\"entity.core/authored/identity-address-43\",\"entity.core/authored/identity-address-44\",\"entity.core/authored/identity-address-45\",\"entity.core/authored/identity-address-46\",\"entity.core/authored/identity-address-47\",\"entity.core/authored/identity-rejects-untyped-and-invalid-public-address-values\",\"entity.core/authored/invariant-cannot-read-arguments\",\"entity.core/authored/invariants-judge-the-post-operation-fields\",\"entity.core/authored/kernel-condition-after\",\"entity.core/authored/kernel-condition-all\",\"entity.core/authored/kernel-condition-any\",\"entity.core/authored/kernel-condition-before\",\"entity.core/authored/kernel-condition-contains\",\"entity.core/authored/kernel-condition-eq\",\"entity.core/authored/kernel-condition-exists\",\"entity.core/authored/kernel-condition-gt\",\"entity.core/authored/kernel-condition-gte\",\"entity.core/authored/kernel-condition-in\",\"entity.core/authored/kernel-condition-lt\",\"entity.core/authored/kernel-condition-lte\",\"entity.core/authored/kernel-condition-missing\",\"entity.core/authored/kernel-condition-ne\",\"entity.core/authored/kernel-condition-not\",\"entity.core/authored/kernel-condition-prefix\",\"entity.core/authored/kernel-condition-prefix-case-sensitive\",\"entity.core/authored/kernel-condition-suffix\",\"entity.core/authored/kernel-rejects-service-only-declaration-families\",\"entity.core/authored/legacy-event-fold-checks-every-recorded-operation-fact\",\"entity.core/authored/legacy-event-rehydration-refuses-service1\",\"entity.core/authored/legacy-event-rehydration-refuses-service2\",\"entity.core/authored/legacy-event-rehydration-refuses-service3\",\"entity.core/authored/legacy-event-rehydration-rejects-changed-payload\",\"entity.core/authored/legacy-event-rehydration-rejects-missing-duplicate\",\"entity.core/authored/legacy-event-rehydration-rejects-revision-gap\",\"entity.core/authored/legacy-event-rehydration-retains-duplicate-events\",\"entity.core/authored/logical-identity-must-match-storage-address\",\"entity.core/authored/missing-and-null-references-differ-from-literal-null\",\"entity.core/authored/nested-defaults-apply-only-to-supplied-objects\",\"entity.core/authored/nested-quantifier-binders-shadow-only-their-inner-scope\",\"entity.core/authored/nested-reference-must-be-declared\",\"entity.core/authored/normalization-applies-argument-defaults\",\"entity.core/authored/numeric-adjacent-integers-past-binary64\",\"entity.core/authored/numeric-kernel-exact-door\",\"entity.core/authored/numeric-two-wire-doors\",\"entity.core/authored/numeric-underflow-remains-stored\",\"entity.core/authored/numeric-wire-and-literal-doors\",\"entity.core/authored/operation-cannot-change-identity-mirror\",\"entity.core/authored/owns-carriers-are-required-on-the-target\",\"entity.core/authored/post-set-fields-are-revalidated\",\"entity.core/authored/precondition-cannot-read-next-state\",\"entity.core/authored/preload-payment-negative\",\"entity.core/authored/preload-payment-positive\",\"entity.core/authored/preload-payment-zero\",\"entity.core/authored/preload-quantifiers-preserve-vacuity-and-dominating-truth\",\"entity.core/authored/preload-validates-input-before-refusal\",\"entity.core/authored/prepared-continuation-binds-subject-and-matches-direct-result\",\"entity.core/authored/projection-references-and-state-are-validated\",\"entity.core/authored/references-carrier-kind-comes-from-target-identity\",\"entity.core/authored/references-require-a-target-declaration-and-nonblank-value\",\"entity.core/authored/registration-lifecycle-defects-do-not-cascade\",\"entity.core/authored/registry-duplicate-and-invalid-replacement-preserve-definition\",\"entity.core/authored/registry-mutual-nested-and-argument-references-resolve-together\",\"entity.core/authored/registry-refuses-two-owners-of-one-entity\",\"entity.core/authored/registry-relations-check-carrier-on-target\",\"entity.core/authored/registry-resolves-reference-targets-as-a-set\",\"entity.core/authored/rehydrate-rejects-empty-history\",\"entity.core/authored/relation-carrier-optionality-and-single-claim-are-enforced\",\"entity.core/authored/replay-rejects-empty-history\",\"entity.core/authored/replay-rejects-fields-tampering\",\"entity.core/authored/replay-rejects-identity-tampering\",\"entity.core/authored/replay-rejects-missing-definition-tampering\",\"entity.core/authored/replay-rejects-revision-tampering\",\"entity.core/authored/replay-rejects-state-tampering\",\"entity.core/authored/replay-zero-event-genesis\",\"entity.core/authored/returned-kernel1-decisions-replay-and-reject-tampering\",\"entity.core/authored/returned-service1-decisions-replay-and-reject-tampering\",\"entity.core/authored/returned-service2-decisions-replay-and-reject-tampering\",\"entity.core/authored/returned-service3-decisions-replay-and-reject-tampering\",\"entity.core/authored/revision-exhaustion-refuses-before-effects\",\"entity.core/authored/rule-refusals-retain-names-and-default-messages\",\"entity.core/authored/runtime-executes-the-exact-instance-definition\",\"entity.core/authored/runtime-refuses-missing-registry-version\",\"entity.core/authored/schema-constraints-refuse-every-inapplicable-family\",\"entity.core/authored/service-map-and-union-declarations-and-outer-values-are-closed\",\"entity.core/authored/service-outcome-names-defaults-and-observability\",\"entity.core/authored/service-quantifiers-validate-binders-collections-operands-and-depth\",\"entity.core/authored/service-responses-must-be-declared-and-complete\",\"entity.core/authored/service-wrong-state-and-effect-declarations-are-closed\",\"entity.core/authored/service1-adjacent-union-preserves-tag-and-value\",\"entity.core/authored/service1-array-ordinal\",\"entity.core/authored/service1-bare-state-guard-is-a-selector\",\"entity.core/authored/service1-binary64-refuses-nonfinite-magnitude\",\"entity.core/authored/service1-binary64-retains-negative-zero\",\"entity.core/authored/service1-collection-count\",\"entity.core/authored/service1-compare-missing\",\"entity.core/authored/service1-compare-number\",\"entity.core/authored/service1-compare-ranked-text\",\"entity.core/authored/service1-first-matching-outcome-wins\",\"entity.core/authored/service1-forall-empty\",\"entity.core/authored/service1-forall-false\",\"entity.core/authored/service1-forall-true\",\"entity.core/authored/service1-forany-empty\",\"entity.core/authored/service1-forany-missing\",\"entity.core/authored/service1-forany-true\",\"entity.core/authored/service1-map-count-not-count-key\",\"entity.core/authored/service1-map-member-errors-retain-paths\",\"entity.core/authored/service1-map-quantified-values\",\"entity.core/authored/service1-map-values-are-typed\",\"entity.core/authored/service1-no-matching-outcome-is-typed\",\"entity.core/authored/service1-rank-disagreement\",\"entity.core/authored/service1-rank-without-scale\",\"entity.core/authored/service1-selection-fallback\",\"entity.core/authored/service1-selection-fast\",\"entity.core/authored/service1-selection-terminal\",\"entity.core/authored/service1-selection-wrong-source\",\"entity.core/authored/service1-state-guard-skips-unobservable-input\",\"entity.core/authored/service1-truthy-missing\",\"entity.core/authored/service1-truthy-true\",\"entity.core/authored/service1-truthy-zero\",\"entity.core/authored/service1-union-refuses-unknown-tag\",\"entity.core/authored/service1-unknown-negation\",\"entity.core/authored/service2-presence-absent\",\"entity.core/authored/service2-presence-null\",\"entity.core/authored/service2-presence-present\",\"entity.core/authored/service2-presence-rejects-null-string\",\"entity.core/authored/service3-direct-execute-requires-fulfillment\",\"entity.core/authored/service3-fulfillment-extra-coordinate\",\"entity.core/authored/service3-fulfillment-invalid-value\",\"entity.core/authored/service3-fulfillment-missing-coordinate\",\"entity.core/authored/service3-fulfillment-preserve\",\"entity.core/authored/service3-fulfillment-remove-required\",\"entity.core/authored/service3-fulfillment-set-remove\",\"entity.core/authored/service3-refusal-needs-no-loaded-instance\",\"entity.core/authored/silent-execution-advances-once-with-no-events\",\"entity.core/authored/templates-recurse-escape-dollar-and-read-post-set-fields\",\"entity.core/authored/timestamp-48\",\"entity.core/authored/timestamp-49\",\"entity.core/authored/timestamp-50\",\"entity.core/authored/timestamp-unknown-diagnostics-and-literal-validation\",\"entity.core/authored/transition-refusal-precedes-failed-precondition\",\"entity.core/authored/unknown-operation-is-typed\",\"entity.core/authored/unknown-rules-do-not-become-false\",\"entity.core/authored/unknown-state-precedes-unknown-operation\",\"entity.core/authored/unobservable-rules-report-sorted-unique-addresses\",\"entity.core/authored/unresolved-json-template-path-is-an-error\",\"entity.executor.Batch/outcome/returned\",\"entity.executor.CancelBatch/outcome/returned\",\"entity.executor.CapturedAppend/outcome/returned\",\"entity.executor.CapturedMerge/outcome/returned\",\"entity.executor.ClearRegistry/outcome/returned\",\"entity.executor.ClearTrace/outcome/returned\",\"entity.executor.Create/outcome/returned\",\"entity.executor.Execute/outcome/returned\",\"entity.executor.History/outcome/returned\",\"entity.executor.HistoryCoordinates/outcome/returned\",\"entity.executor.Load/outcome/returned\",\"entity.executor.LookupReceipt/outcome/returned\",\"entity.executor.LookupRecord/outcome/returned\",\"entity.executor.Observe/outcome/returned\",\"entity.executor.RecordingRefusals/outcome/returned\",\"entity.executor.RefusalReasons/outcome/returned\",\"entity.executor.Refusals/outcome/returned\",\"entity.executor.Register/outcome/returned\",\"entity.executor.ScriptAppend/outcome/returned\",\"entity.executor.SeedImported/outcome/returned\",\"entity.executor.SupplyHistory/outcome/returned\",\"entity.executor.SupplyLoadFailure/outcome/returned\",\"entity.executor.Tamper/outcome/returned\",\"entity.executor.Trace/outcome/returned\",\"entity.executor.VerifyComplete/outcome/returned\",\"entity.executor/authored/changed-batch-order-conflicts\",\"entity.executor/authored/changed-request-conflicts\",\"entity.executor/authored/complete-decisions-retain-duplicate-events\",\"entity.executor/authored/corrupt-history-never-recovers-success\",\"entity.executor/authored/dropped-pending-response-is-not-rollback\",\"entity.executor/authored/dropping-before-poll-dispatches-nothing\",\"entity.executor/authored/duplicate-identities-refuse-before-io\",\"entity.executor/authored/empty-batches-perform-no-io\",\"entity.executor/authored/empty-history-is-not-a-merge\",\"entity.executor/authored/executor-input-refusal-existing-create\",\"entity.executor/authored/executor-input-refusal-missing-execute\",\"entity.executor/authored/global-record-identities-span-subjects-and-kinds\",\"entity.executor/authored/historical-observation-has-no-committed-receipt\",\"entity.executor/authored/imported-exact-retry-is-historical\",\"entity.executor/authored/imported-missing-definition-cannot-prove-retry\",\"entity.executor/authored/issue-50-batch-refusal-is-atomic\",\"entity.executor/authored/issue-50-malformed-version\",\"entity.executor/authored/issue-50-versioned-merge-preserves-proof\",\"entity.executor/authored/kernel-refusal-is-recorded\",\"entity.executor/authored/malformed-create-is-rejected-before-io\",\"entity.executor/authored/merge-appends-over-observation-tips\",\"entity.executor/authored/merge-candidate-uses-first-state-and-all-tips\",\"entity.executor/authored/merge-checks-head-and-highest-revision\",\"entity.executor/authored/mixed-batch-orders-decisions-and-observations\",\"entity.executor/authored/named-and-single-retries-keep-original-receipts\",\"entity.executor/authored/optional-refusal-recorder-sees-conflicts-not-malformed-input\",\"entity.executor/authored/ordinary-forked-write-records-refusal\",\"entity.executor/authored/refusal-ignores-success-fulfillments\",\"entity.executor/authored/saved-definition-retry-precedes-current-authority\",\"entity.executor/authored/service-1-saved-definition-retry\",\"entity.executor/authored/service-2-conditional-presence-retry\",\"entity.executor/authored/service-3-fulfillment-retry\",\"entity.executor/authored/single-key-must-match-member\",\"entity.executor/authored/stale-observation-rolls-back-whole-batch\",\"entity.executor/authored/subject-refusal-ignores-success-fulfillments\",\"entity.executor/authored/uncertain-response-recovers-one-original-receipt\",\"entity.executor/authored/unreachable-is-never-absent\",\"entity.query.Continue/outcome/returned\",\"entity.query.DecodeCursor/outcome/returned\",\"entity.query.Limit/outcome/returned\",\"entity.query.Ordered/outcome/returned\",\"entity.query.Page/outcome/returned\",\"entity.query.Query/outcome/returned\",\"entity.query.Seed/outcome/returned\",\"entity.query/authored/consumed-descending-identity-refuses\",\"entity.query/authored/consumed-duplicate-identity-refuses\",\"entity.query/authored/consumed-foreign-entity-refuses\",\"entity.query/authored/consumed-prefix-before-cursor-is-validated\",\"entity.query/authored/fixed-dataset-complete-traversal\",\"entity.query/authored/foreign-entity-cursor-refuses\",\"entity.query/authored/foreign-predicate-cursor-refuses\",\"entity.query/authored/malformed-cursors-are-typed-refusals\",\"entity.query/authored/matching-pages-skip-nonmatches\",\"entity.query/authored/nested-numeric-containment\",\"entity.query/authored/numeric-values-remain-exact\",\"entity.query/authored/ordered-api-stops-after-matching-lookahead\",\"entity.query/authored/page-builder-truncates-matches\",\"entity.query/authored/page-limit-boundaries\",\"entity.query/authored/query-document-keys-are-closed\",\"entity.query/authored/recursive-containment\",\"entity.shell.Calls/outcome/returned\",\"entity.shell.ClearCalls/outcome/returned\",\"entity.shell.ClearRegistry/outcome/returned\",\"entity.shell.Create/outcome/returned\",\"entity.shell.Events/outcome/returned\",\"entity.shell.Execute/outcome/returned\",\"entity.shell.Fault/outcome/returned\",\"entity.shell.Get/outcome/returned\",\"entity.shell.List/outcome/returned\",\"entity.shell.Open/outcome/returned\",\"entity.shell.Race/outcome/returned\",\"entity.shell.Records/outcome/returned\",\"entity.shell.Register/outcome/returned\",\"entity.shell.Reopen/outcome/returned\",\"entity.shell.Replace/outcome/returned\",\"entity.shell/authored/file-changed-retry-conflicts\",\"entity.shell/authored/file-create-retry-after-advancement\",\"entity.shell/authored/file-invalid-recording-publishes-nothing\",\"entity.shell/authored/file-missing-subjects\",\"entity.shell/authored/file-provider-failures-preserve-classification\",\"entity.shell/authored/file-retry-uses-original-record\",\"entity.shell/authored/file-revision-rechecked-at-commit\",\"entity.shell/authored/file-round-trip\",\"entity.shell/authored/file-stale-intent-precedes-kernel\",\"entity.shell/authored/kernel-refusal-precedes-recording-validation\",\"entity.shell/authored/memory-changed-retry-conflicts\",\"entity.shell/authored/memory-create-retry-after-advancement\",\"entity.shell/authored/memory-invalid-recording-publishes-nothing\",\"entity.shell/authored/memory-missing-subjects\",\"entity.shell/authored/memory-retry-uses-original-record\",\"entity.shell/authored/memory-revision-rechecked-at-commit\",\"entity.shell/authored/memory-round-trip\",\"entity.shell/authored/memory-stale-intent-precedes-kernel\",\"entity.shell/authored/provider-failures-preserve-classification\",\"entity.shell/authored/retry-does-not-hide-read-failure\",\"entity.store.AcquireLegacy/outcome/returned\",\"entity.store.Append/outcome/returned\",\"entity.store.AtomicBatch/outcome/returned\",\"entity.store.BatchCoordinate/outcome/returned\",\"entity.store.BranchHeads/outcome/returned\",\"entity.store.BranchTips/outcome/returned\",\"entity.store.CanonicalBytes/outcome/returned\",\"entity.store.CheckExpectation/outcome/returned\",\"entity.store.ClearTrace/outcome/returned\",\"entity.store.Commit/outcome/returned\",\"entity.store.CommitRecorded/outcome/returned\",\"entity.store.Events/outcome/returned\",\"entity.store.FileExists/outcome/returned\",\"entity.store.History/outcome/returned\",\"entity.store.Ids/outcome/returned\",\"entity.store.Load/outcome/returned\",\"entity.store.LookupBatch/outcome/returned\",\"entity.store.LookupRecord/outcome/returned\",\"entity.store.MemberCoordinate/outcome/returned\",\"entity.store.MigrateFile/outcome/returned\",\"entity.store.Observations/outcome/returned\",\"entity.store.Observe/outcome/returned\",\"entity.store.Project/outcome/returned\",\"entity.store.ReadFileFixture/outcome/returned\",\"entity.store.ReadRecordDomain/outcome/returned\",\"entity.store.RecordDomains/outcome/returned\",\"entity.store.RecordFraming/outcome/returned\",\"entity.store.Records/outcome/returned\",\"entity.store.Reopen/outcome/returned\",\"entity.store.RequestBytes/outcome/returned\",\"entity.store.Seal/outcome/returned\",\"entity.store.SeedImported/outcome/returned\",\"entity.store.SetPosition/outcome/returned\",\"entity.store.SubjectCoordinate/outcome/returned\",\"entity.store.Tamper/outcome/returned\",\"entity.store.Trace/outcome/returned\",\"entity.store.ValidateEnvelope/outcome/returned\",\"entity.store.ValidateObservation/outcome/returned\",\"entity.store.ValidateRecordedCommit/outcome/returned\",\"entity.store.VerifyComplete/outcome/returned\",\"entity.store.VerifyExplicit/outcome/returned\",\"entity.store.VerifySubject/outcome/returned\",\"entity.store.WriteFileFixture/outcome/returned\",\"entity.store/authored/an-absent-memory-subject-is-none\",\"entity.store/authored/canonical-byte-encoding-preserves-exact-number-tokens\",\"entity.store/authored/complete-assurance-requires-provider-capture\",\"entity.store/authored/coordinates-retain-opaque-subjects-and-disjoint-batch-namespaces\",\"entity.store/authored/envelope-null-provenance-is-explicit\",\"entity.store/authored/expectation-absent-is-distinct-from-revision-zero\",\"entity.store/authored/file-absence\",\"entity.store/authored/file-corrupt-marker-is-not-absence\",\"entity.store/authored/file-exact-retry-after-state-advances\",\"entity.store/authored/file-global-record-identity\",\"entity.store/authored/file-invalid-recording-is-inert\",\"entity.store/authored/file-listed-identities-are-sorted\",\"entity.store/authored/file-observations-do-not-advance\",\"entity.store/authored/file-ordered-duplicate-events\",\"entity.store/authored/file-reopen\",\"entity.store/authored/file-stale-write-preserves-state\",\"entity.store/authored/imported-boundary-retains-limited-assurance\",\"entity.store/authored/imported-evidence-cannot-exceed-anchor\",\"entity.store/authored/legacy-acquisition-of-absent-source-is-read-only\",\"entity.store/authored/legacy-acquisition-preserves-only-known-order\",\"entity.store/authored/lineage-decisions-produce-two-heads\",\"entity.store/authored/memory-absence\",\"entity.store/authored/memory-atomic-order\",\"entity.store/authored/memory-atomic-rollback\",\"entity.store/authored/memory-empty-batch\",\"entity.store/authored/memory-exact-retry-after-state-advances\",\"entity.store/authored/memory-global-record-identity\",\"entity.store/authored/memory-invalid-recording-is-inert\",\"entity.store/authored/memory-listed-identities-are-sorted\",\"entity.store/authored/memory-observations-do-not-advance\",\"entity.store/authored/memory-ordered-duplicate-events\",\"entity.store/authored/memory-stale-write-preserves-state\",\"entity.store/authored/migration-dry-run-and-out-of-place\",\"entity.store/authored/migration-invalid-source-publishes-nothing\",\"entity.store/authored/observation-revisions-are-positive-signed-range\",\"entity.store/authored/observations-are-tips-but-not-decision-heads\",\"entity.store/authored/older-framing-reader-refuses-before-payload\",\"entity.store/authored/projection-groups-sorted-identities\",\"entity.store/authored/projection-scalar-and-nested-keys\",\"entity.store/authored/record-domain-kernel-1\",\"entity.store/authored/record-domain-service-1\",\"entity.store/authored/record-domain-service-2\",\"entity.store/authored/record-domain-service-3\",\"entity.store/authored/recorded-commit-result-must-match-instance\",\"entity.store/authored/recorded-memory-atomic-observation-order\",\"entity.store/authored/recorded-memory-atomic-rollback\",\"entity.store/authored/recorded-memory-writer-revalidates-public-members\",\"entity.store/authored/recorded-position-overflow-is-atomic\",\"entity.store/authored/tampered-complete-history-is-rejected\"],\"selection\":{\"filter\":{\"kind\":\"all\"},\"origins\":\"generated_and_authored\",\"scope\":{\"kind\":\"system\"}},\"spec_digest\":\"a13e6c07be82c913363ea52b8042c6e379343acfda7fcc03a212c0942569da5d\",\"specification\":\"entity/v1\",\"suite\":{\"digest\":\"sha256:53c11c53dab9e23d4b05f25cc8b5c3a6da92572d3a2b53ba2f394bd0735dd9e6\",\"digest_profile\":\"sha256-json-bytes/1\",\"version\":\"ess-conformance/29\"},\"suite_input\":\"target/issue-wave-scratch/issue50-conformance/suite.json\"}", + "reference": "target/issue-wave-scratch/issue50-conformance/report.json" + } +} diff --git a/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152345Z-000-d0fa91cb101d.json b/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152345Z-000-d0fa91cb101d.json new file mode 100644 index 00000000..98636ce8 --- /dev/null +++ b/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152345Z-000-d0fa91cb101d.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T15:23:45Z", + "actor": "human:timo", + "artifact": "story:executor-input-refusal-before-existence", + "kind": "story", + "revision": 12, + "change": { + "change": "evidence", + "kind": "verification", + "source": "Original two RevisionConflict reproductions red before fix; 38 package tests green after implementation and independent review", + "reference": "review-result:issue-50-adversary" + } +} diff --git a/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152840Z-000-efef8a62f8bf.json b/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152840Z-000-efef8a62f8bf.json new file mode 100644 index 00000000..0da8d3c3 --- /dev/null +++ b/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T152840Z-000-efef8a62f8bf.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T15:28:40Z", + "actor": "human:timo", + "artifact": "story:executor-input-refusal-before-existence", + "kind": "story", + "revision": 12, + "change": { + "change": "evidence", + "kind": "test_result", + "source": "CARGO_PROFILE_DEV_DEBUG=0 CARGO_BUILD_JOBS=2 task check exited 0 on integrated #49 and #50; PostgreSQL lane explicitly skipped because ENTITY_POSTGRES_URL unset", + "reference": "target/issue-wave-scratch/task-check.log" + } +} diff --git a/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T163923Z-000-ba1db0d84a15.json b/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T163923Z-000-ba1db0d84a15.json new file mode 100644 index 00000000..3b66e57d --- /dev/null +++ b/.engineering/evidence/story/executor-input-refusal-before-existence/20261003T163923Z-000-ba1db0d84a15.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T16:39:23Z", + "actor": "human:timo", + "artifact": "story:executor-input-refusal-before-existence", + "kind": "story", + "revision": 13, + "change": { + "change": "evidence", + "kind": "test_result", + "source": "Final all-issue integration task check exit 0 with 421 original ESS scenarios passing and real PostgreSQL execution", + "reference": "docs/ess/evidence/provider-tracking/README.md" + } +} diff --git a/.engineering/evidence/task/release-0-26-0/20261003T183922Z-000-2666ee3c5edb.json b/.engineering/evidence/task/release-0-26-0/20261003T183922Z-000-2666ee3c5edb.json new file mode 100644 index 00000000..64d81a90 --- /dev/null +++ b/.engineering/evidence/task/release-0-26-0/20261003T183922Z-000-2666ee3c5edb.json @@ -0,0 +1,13 @@ +{ + "at": "2026-10-03T18:39:22Z", + "actor": "human:timo", + "artifact": "task:release-0-26-0", + "kind": "task", + "revision": 3, + "change": { + "change": "evidence", + "kind": "test_result", + "source": "task check on the 0.26.0 candidate with a real PostgreSQL 17.6 fixture; exit 0", + "reference": "target/release-evidence/task-check.log" + } +} diff --git a/.engineering/planning/decision-blocker/sqlite-cache-integrity-boundary.md b/.engineering/planning/decision-blocker/sqlite-cache-integrity-boundary.md new file mode 100644 index 00000000..f1743e5d --- /dev/null +++ b/.engineering/planning/decision-blocker/sqlite-cache-integrity-boundary.md @@ -0,0 +1,27 @@ +--- +format: aep.planning-md/3 +id: decision-blocker:sqlite-cache-integrity-boundary +kind: decision-blocker +status: cleared +title: Choose the integrity boundary for SQLite warm cache verification +relations: +- blocks: story:bounded-batch-and-facade-reads +revision: 3 +transitions: +- {from: "open", to: "cleared", at: "2026-10-03T15:26:44Z", actor: "human:timo", revision: 3} +--- +## Question + +The operator was asked whether a full verification on open followed by SQLite change tracking may establish unchanged state, detecting writes from any SQLite connection including SQL tampering, or whether every read must detect arbitrary raw database-file edits bypassing SQLite. No answer has arrived yet. + +## Consequence + +The narrowed SQLite boundary admits investigation of a provider-owned checkpoint and atomic append delta. The arbitrary-file boundary does not admit returning a cached answer merely because SQLite reports no change. Keep current complete verification until the operator answers; do not infer agreement from silence. + +## Clear condition + +Record the operator's chosen integrity boundary and reflect it in the provider contract and executable verification cases. This clears a decision only, not dependency-blocker:verified-provider-change-authority or any performance evidence requirement. + +## Operator decision + +The operator explicitly answered "Accept SQLite change tracking" on 2026-10-03. Full verification on open remains required. Warm reads may rely on SQLite-mediated change tracking and need not detect raw database-file modifications that bypass SQLite. Writes from other connections, including SQL tampering, must invalidate the cache. This resolves the integrity-boundary choice; provider change authority and implementation/performance evidence remain outstanding. diff --git a/.engineering/planning/dependency-blocker/verified-provider-change-authority.md b/.engineering/planning/dependency-blocker/verified-provider-change-authority.md new file mode 100644 index 00000000..009da02f --- /dev/null +++ b/.engineering/planning/dependency-blocker/verified-provider-change-authority.md @@ -0,0 +1,28 @@ +--- +format: aep.planning-md/3 +id: dependency-blocker:verified-provider-change-authority +kind: dependency-blocker +status: cleared +title: Issue 51 needs an integrity-preserving bounded-read provider contract +relations: +- blocks: story:bounded-batch-and-facade-reads +withholds: test_result +revision: 3 +transitions: +- {from: "open", to: "cleared", at: "2026-10-03T16:38:23Z", actor: "human:timo", revision: 3} +--- +## Evidence + +The read-only scoping of issue #51 found no verified changed-content token or checkpoint authority in pinned Eventlog rev 0a0484634e8c640be29d6b6541d6cc1c1aaef7d3. See crates/eventlog-core/src/capture.rs:273-336 and src/lib.rs:1005-1017,1149-1179 in that revision; ER's adapter/scoped.rs:291-299 and adapter.rs:4159 require batch co-member closure for existing verification. + +## Clear condition + +An accepted, implemented and tested provider contract or an equally strong ER verification design demonstrates bounded shared-clock reads while detecting altered blobs and preserving complete batch assurance. The 55/601/1203 timing probe must meet the issue's 2x bound. A read-scope routing change alone does not supply this evidence. + +## Next owner and action + +Coordinator proposes the provider contract and cross-repository scope to the operator. Do not claim issue #51 fixed or weaken existing integrity assertions. Local facade work may proceed as an explicitly partial contribution after wave approval. + +## Resolution + +The operator accepted the SQLite-visible integrity boundary. Eventlog6983cc25eb92e07844b3a6fa3e0decbdb300f43c implements the optional proof contract and is bot-published with signed common checks, independent regression review and a final545-case PostgreSQL/TLS proof. Runtime1516e748 plus integration receipt/test/docs work preserves full-open verification, complete-new-batch assurance, and external SQL tamper refusal. The exact published-pin integration gate exits0 with421+17 ESS scenarios, all supported and none skipped. Final release probe medians11.532148/13.049370/13.568977ms at55/601/1203 events give1.132/1.177x growth and pass the unchanged2x assertion. Evidence is retained in docs/ess/evidence/provider-tracking and the immutable review records. Clear condition met without changing default constructor behavior or claiming consumer adoption. diff --git a/.engineering/planning/design/github-issues-integration-wave.md b/.engineering/planning/design/github-issues-integration-wave.md new file mode 100644 index 00000000..b27ea7cc --- /dev/null +++ b/.engineering/planning/design/github-issues-integration-wave.md @@ -0,0 +1,363 @@ +--- +format: aep.planning-md/3 +id: design:github-issues-integration-wave +kind: design +status: draft +title: 'GitHub issues 49–51: one integration branch and one PR' +relations: +- designs: story:declared-refusal-before-fulfillment-validation +- designs: story:executor-input-refusal-before-existence +- designs: story:bounded-batch-and-facade-reads +revision: 17 +--- +## Stage and authority + +Skill version 0.19.1 — aep:implementing, wave mode. The operator approved the sequence on 2026-10-03, including the explicit-version API design for #50 and investigation of Eventlog changes for #51. Stage 2 starts now. Approval covers necessary scoped commits, unit integration, publication and one Entity Runtime PR; no release or main merge is requested. The #51 blocker remains until actual integrity-preserving evidence clears it. + +## Proposed sequence + +1. Wave of one: story:declared-refusal-before-fulfillment-validation (#49), serving vision:O2. Preserve selected executor refusals; add stored-field-guard regressions and actual executor ESS cases. Kernel behavior already has the required order. Scope is cited except proposed new scenario filenames. +2. After accepting the explicit version contract, story:executor-input-refusal-before-existence (#50), serving vision:O2. Evaluate creation refusals before existence. Add a caller-version-bound execution entry point for missing subjects while preserving existing row-derived APIs and retry identity. The design is a recommendation, not accepted authority. Scope is cited with inferred new test path. +3. story:bounded-batch-and-facade-reads (#51), serving vision:O2, remains blocked for complete acceptance by dependency-blocker:verified-provider-change-authority. Explicit scoped facade APIs are a feasible local contribution but do not close the issue. Resolve bounded verification/checkpoint or verified-change authority and provider ESS coverage first. Cross-repository Eventlog work is a separate scope decision; do not silently start it or claim this ER-only PR resolves the complete ask. + +All issue work is retained on fix/github-issues-49-51 and ultimately proposed in one ER PR. #49 and #50 overlap Executor::decide and ESS files, so they are not parallel implementation units. Shared changelog, requirements, ESS regeneration and planning writes belong to the coordinator. The computed waves below are candidates, not readiness: they do not remove #51's blocker. + +## Managed checkout and recovery + +Integration id: github-issues-20261003; branch: fix/github-issues-49-51; base/head: 7462b3812d0f8ca16a867ca70cf81a0955c55faa. Managed path relative to HOME: .local/state/worktree/trees/b10x/entity-runtime/github-issues-20261003. Session owner: er-issues-20261003. Stage: scoped proposal, uncommitted planning records only. Unit trees and branches have not been created. The worktree is intentionally retained for the operator's wave decision; next owner is the coordinator in this session. + +Build directory: target/issue-wave-preflight within the integration tree. Scratch root: target/issue-wave-scratch (reserved, not yet created). Future units receive their own managed tree, target and scratch root; no shared build directory. The repository primary checkout remains clean on main. Four earlier linked trees exist, ownership/completion not established; none has been removed or treated as abandoned. + +## Pre-flight and validation + +Initial free space was 14 GiB; it later rose to 18,088,456,192 bytes while other sessions were active. Build concurrency is one unit, two Cargo jobs; recheck before launch and stop new builds below a 5 GiB floor. sccache is configured machine-wide. Model concurrency limit was asked; no answer has yet arrived, so at most three read-only workers and one building implementor are proposed. The baseline executor build completed in 8.60 seconds with CARGO_PROFILE_DEV_DEBUG=0; cargo test -p entity-executor --locked exited 0, executing 17 integration tests and no unit/doc tests. This is baseline evidence only, not a regression verification or full gate. + +No previous-wave cleanup or source edits are authorized by inference from tree age. Audit any old wave identified as unfinished before implementation. Full Eventlog/ESS build size is not yet measured, so do not extrapolate the executor measurement to a concurrent provider build. + +## Roles and commit boundary + +Read-only scopers used the aep:story-scoper procedure through the host's generic collaboration agents; plugin-specific subagent_type dispatch is unavailable. The same host adaptation will use aep:implementor, aep:adversary and aep:security-reviewer procedures independently. No implementation workers have run. This is issue triage into standalone stories, not decomposition of a new parent artifact; no four-critic decomposition panel was run. + +Approval of the first wave authorizes its unit commit, merge into the integration branch, and coordinator planning/evidence commit. The original one-PR request already authorizes publication of the finished integration branch and one bot-authored PR. Later units require their stated contract/readiness conditions; no tag, release or main merge is included. Before opening the finished PR run task check to exit 0 and required site validation if website files change. Record baseline/treatment evidence and independent adversarial review; never mark #51 closed on partial facade changes. + +## Computed candidate waves + +The following is the exact output of aep plan artifact waves --kind story --status draft --format json. Its unrelated unassessed backlog entries are outside the three GitHub issue candidates and deliberately excluded from this task; all three issue candidates have been scoped. No unassessed issue is proposed for dispatch. + +{ + "waves": [ + { + "wave": 1, + "artifacts": [ + { + "id": "story:bounded-batch-and-facade-reads", + "inferred": false, + "scope": [ + { + "confidence": "cited", + "path": "CHANGELOG.md" + }, + { + "confidence": "cited", + "path": "crates/entity-eventlog" + }, + { + "confidence": "cited", + "path": "crates/entity-eventlog/src/adapter.rs" + }, + { + "confidence": "cited", + "path": "crates/entity-eventlog/src/adapter/scoped.rs" + }, + { + "confidence": "cited", + "path": "crates/entity-eventlog/src/adapter/small_store_cost.rs" + }, + { + "confidence": "cited", + "path": "crates/entity-eventlog/src/facade.rs" + }, + { + "confidence": "cited", + "path": "crates/entity-eventlog/src/sync.rs" + }, + { + "confidence": "cited", + "path": "crates/entity-eventlog/tests/adversary_r2_per_entity_reads.rs" + }, + { + "confidence": "cited", + "path": "crates/entity-eventlog/tests/per_entity_reads.rs" + }, + { + "confidence": "cited", + "path": "crates/entity-eventlog/tests/provider_facades.rs" + }, + { + "confidence": "cited", + "path": "docs/design/eventlog-recorded-adapter-v0.1.md" + }, + { + "confidence": "cited", + "path": "docs/design/eventlog-recorded-sync-bridge-v0.1.md" + }, + { + "confidence": "cited", + "path": "docs/requirements.md" + } + ] + } + ] + }, + { + "wave": 2, + "artifacts": [ + { + "id": "story:declared-refusal-before-fulfillment-validation", + "inferred": true, + "scope": [ + { + "confidence": "cited", + "path": "CHANGELOG.md" + }, + { + "confidence": "cited", + "path": "crates/entity-executor/src/lib.rs" + }, + { + "confidence": "cited", + "path": "crates/entity-executor/tests/service_3_fulfillment_retry.rs" + }, + { + "confidence": "cited", + "path": "docs/ess/executor-traceability.md" + }, + { + "confidence": "cited", + "path": "ess/coverage.json" + }, + { + "confidence": "cited", + "path": "ess/ess-inputs.yaml" + }, + { + "confidence": "cited", + "path": "ess/generated/model.json" + }, + { + "confidence": "cited", + "path": "ess/generated/suite.json" + }, + { + "confidence": "inferred", + "path": "ess/scenarios/executor/refusal-ignores-success-fulfillments.yaml" + }, + { + "confidence": "inferred", + "path": "ess/scenarios/executor/subject-refusal-ignores-success-fulfillments.yaml" + } + ] + } + ] + }, + { + "wave": 3, + "artifacts": [ + { + "id": "story:executor-input-refusal-before-existence", + "inferred": true, + "scope": [ + { + "confidence": "cited", + "path": "CHANGELOG.md" + }, + { + "confidence": "cited", + "path": "checks/ess-conformance/src/executor.rs" + }, + { + "confidence": "cited", + "path": "crates/entity-executor/src/lib.rs" + }, + { + "confidence": "inferred", + "path": "crates/entity-executor/tests/input_refusal_precedence.rs" + }, + { + "confidence": "cited", + "path": "docs/design/recorded-execution-v0.1.md" + }, + { + "confidence": "cited", + "path": "docs/design/service-binding-boundary-v0.1.md" + }, + { + "confidence": "cited", + "path": "docs/ess/executor-traceability.md" + }, + { + "confidence": "cited", + "path": "docs/requirements.md" + }, + { + "confidence": "cited", + "path": "ess/coverage.json" + }, + { + "confidence": "cited", + "path": "ess/ess-inputs.yaml" + }, + { + "confidence": "cited", + "path": "ess/generated/model.json" + }, + { + "confidence": "cited", + "path": "ess/generated/suite.json" + }, + { + "confidence": "cited", + "path": "ess/scenarios/executor/" + } + ] + } + ] + } + ], + "collisions": [ + { + "a": "story:bounded-batch-and-facade-reads", + "b": "story:declared-refusal-before-fulfillment-validation", + "path": "CHANGELOG.md", + "confidence": "cited" + }, + { + "a": "story:bounded-batch-and-facade-reads", + "b": "story:executor-input-refusal-before-existence", + "path": "CHANGELOG.md", + "confidence": "cited" + }, + { + "a": "story:bounded-batch-and-facade-reads", + "b": "story:executor-input-refusal-before-existence", + "path": "docs/requirements.md", + "confidence": "cited" + }, + { + "a": "story:declared-refusal-before-fulfillment-validation", + "b": "story:executor-input-refusal-before-existence", + "path": "CHANGELOG.md", + "confidence": "cited" + }, + { + "a": "story:declared-refusal-before-fulfillment-validation", + "b": "story:executor-input-refusal-before-existence", + "path": "crates/entity-executor/src/lib.rs", + "confidence": "cited" + }, + { + "a": "story:declared-refusal-before-fulfillment-validation", + "b": "story:executor-input-refusal-before-existence", + "path": "docs/ess/executor-traceability.md", + "confidence": "cited" + }, + { + "a": "story:declared-refusal-before-fulfillment-validation", + "b": "story:executor-input-refusal-before-existence", + "path": "ess/coverage.json", + "confidence": "cited" + }, + { + "a": "story:declared-refusal-before-fulfillment-validation", + "b": "story:executor-input-refusal-before-existence", + "path": "ess/ess-inputs.yaml", + "confidence": "cited" + }, + { + "a": "story:declared-refusal-before-fulfillment-validation", + "b": "story:executor-input-refusal-before-existence", + "path": "ess/generated/model.json", + "confidence": "cited" + }, + { + "a": "story:declared-refusal-before-fulfillment-validation", + "b": "story:executor-input-refusal-before-existence", + "path": "ess/generated/suite.json", + "confidence": "cited" + } + ], + "unassessed": [ + "story:aep-markdown-materialized-view", + "story:definition-json-schema", + "story:definition-migrations", + "story:explain-verb", + "story:gate-and-release-hardening", + "story:guarded-blob-writes-commit-with-their-group", + "story:named-predicates", + "story:one-git-url-one-rev-across-the-workspace", + "story:pedantic-lints", + "story:provider-feature-combinations-compile-and-are-gated", + "story:recorded-stores-read-the-same-on-a-tree-that-keeps-text-once", + "story:schema-fragments", + "story:seeded-open-under-one-second" + ], + "cycles": [] +} + +## Handoff measurement + +After the baseline build, target/issue-wave-preflight occupies 116,848,903 bytes; free disk is 17,951,981,568 bytes. The build process exited 0 and is no longer running. Its reproducible output is retained for the next wave, not archived as evidence. No branch was published and no PR was created at stage 1. All five new planning files are retained uncommitted in the integration worktree; primary source is unchanged. The coordinator releases only its own session lease at handoff and will reacquire it before resuming. + +## Unit 49 launched + +Managed id issue-49-20261003, branch impl/issue-49-refusal, base 72a9c36fbd58baee10be844a170649692772e51a. Path relative to HOME: .local/state/worktree/trees/b10x/entity-runtime/issue-49-20261003. Build target/, scratch target/issue-49-scratch/, brief target/issue-49-scratch/brief.md. Stage: implementation. Coordinator owns shared ESS artifacts and documents. Opening commit cheap gates fmt-check, req-check, pin-check and notes-check all exited 0; author and committer verified as b10x-bot[bot]. + +## Unit 49 integration and unit 50 launch + +Unit 49 commit c6164443df9f303f4898f1de1abfc1c899e8c921 was fast-forwarded into the integration branch after red/green regression and adversarial checks. Executor package count 17→20→22; all final tests, formatter and Clippy exited 0. The review found no defect. New ESS cases await coordinator regeneration and full conformance gate. Bot author and committer verified. + +Unit 50 managed id issue-50-20261003, branch impl/issue-50-input-refusal, base c6164443df9f303f4898f1de1abfc1c899e8c921. Path relative to HOME: .local/state/worktree/trees/b10x/entity-runtime/issue-50-20261003. Build target/, scratch target/issue-50-scratch/, brief target/issue-50-scratch/brief.md. Stage: implementation of the approved additive version API. Coordinator retains shared manifest/generated/coverage/doc ownership. + +## Conformance checkpoint + +Issue 49's complete conformance check exited 0: {"total":416,"passed":416,"failed":0,"error":0,"unsupported":0,"skipped":0}. The prior 414 scenario contracts were all unchanged; two authored scenarios were added. Commit b01d15f0 contains the exact regenerated suite and machine-readable evidence. The raw adversary record issue-49-adversary-pass-1 contains a local compiler path and remains private/untracked; the public record issue-49-adversary-public explicitly redacts only that path. Do not stage the private record. It is retained for local archival with raw logs. + +## Integrated second unit + +Issue #50 is merged: unit commit be0ce7e4, integration merge 201f8fc8. Independent review-result:issue-50-adversary found no defect and added three tests (35 to 38); production source was unchanged during review. Integration corrected two scenario identifiers to ESS hyphen syntax after generation rejected underscores. Conformance generation and execution now succeed: 421 total, 421 passed, zero failed/error/unsupported/skipped. Coverage diff: 416 to 421, removed [], changed_existing []. Exact report/2 and suite are retained in integration scratch issue50-conformance/; AEP imported the exact pair. The full repository gate is running. Issue #51 remains blocked pending the explicit integrity-boundary choice and a provider-owned change-proof contract. + +## Third unit unblocked by operator choice + +The operator explicitly accepted SQLite change tracking on 2026-10-03: full verification on open, warm invalidation for SQLite-mediated writes including external-connection tampering, raw database-file edits bypassing SQLite outside that warm guarantee. The integrity decision blocker is cleared. Continue #51 under the approved all-issues scope; do not create the integration PR prematurely. Scope/design agents are assessing a generic optional Eventlog provider capability and the runtime incremental verifier before dispatch. Upstream source support may require a bot-published dependency commit; retain exactly one Entity Runtime integration PR, without an unrelated release or main merge. + +## First integrated gate + +CARGO_PROFILE_DEV_DEBUG=0 CARGO_BUILD_JOBS=2 task check exited 0 for integrated #49 and #50. All ordered steps completed: formatting, workspace Clippy and tests, documentation, examples, requirements, fixture pins, Eventlog all-feature Rust 1.91 tests/Clippy, notes and ESS. PostgreSQL's dedicated server lane explicitly skipped because ENTITY_POSTGRES_URL was unset; it is not claimed as executed. ESS final output: {"total":421,"passed":421,"failed":0,"error":0,"unsupported":0,"skipped":0}. Exact log and exit status remain in target/issue-wave-scratch/task-check.log and task-check.exit. The two executor stories are implemented; #51 continues before publication and requires a new integrated gate after its changes. + +## Third unit implementation underway + +Provider unit: Eventlog managed id er-51-capture-20261003, branch fix/er-51-capture-checkpoints, base06c1e99c86c7130169c7ed584ec7326f7e9a546e, scratch target/issue-51-scratch, build target. Runtime unit: Entity Runtime id issue-51-20261003, branch impl/issue-51-bounded-reads, basef57bf9753abec7aaa5ff9103bd9b66e29f3398cd, scratch target/issue-51-scratch, build target. Both source implementations were admitted after their transient ESS declarations validated and coordinator AEP stories moved active. Distinct runtime performance author owns only tests/shared_clock_cost.rs; baseline complete and frozen before source changes compiled. + +Baseline release probe: 55/601/1203 exact events, warm shared-clock batch medians 92.517484ms/7.038153041s/12.449723852s, ratios76.07x/134.57x; intended bound assertion red, exit101. All real-provider seed/history/revision checks passed. Immutable source/test/binary hashes and samples are in runtime scratch perf/. This is baseline evidence only. + +Coordinator's separate provider conformance draft validates 7 authored scenarios and uses a real SQLite/facade adapter; compilation/execution awaits unit integration. Existing 421-scenario five-library scope remains separate. Full task check will include both afterward. A dedicated disposable PostgreSQL TLS fixture has been prepared outside repositories for the next full gates; other sessions' containers are untouched. + +The previously skipped PostgreSQL lane was run against the task-owned PostgreSQL17.6 fixture and exited0: 13 provider conformance tests passed; the feature-disabled facade target and doc-test target each selected zero tests. Initial fixture attempt failed to connect because Docker reassigned the ephemeral host port during TLS restart; reinspecting the container port corrected setup without a source change. Both raw logs and exit statuses remain in integration scratch. Future integrated gates will use the live fixture and include the server lane. + +## Completed integration + +All three issue stories are implemented. Runtime unit1516e748 was fast-forwarded after independent review; provider6983cc25 is bot-published with signed common checks and its545-test PostgreSQL/TLS proof. All dependency pins/locks use that exact revision. Both introduced review defects (receipt-key parity and same-table projection aliasing) retain original failing cases and separate fixed rechecks. Final integrated task check exits0 with all ordered steps completed; PostgreSQL executes, original ESS421 and new provider ESS17 all pass. Final explicit release performance probe passes with1.177x growth between55 and1203events. No existing scenario contract was removed or changed. + +Per-step result: fmt-check0, clippy0, test0, doc-check0, example-check0, req-check0, pin-check0, postgres-check0, eventlog-runtime-check0, notes-check0, ess-check0, provider-ess-check0. The aggregate fail-fast task exits0; raw output and exit receipt are in assigned scratch. The package-level performance case is intentionally ignored and separately executed once after final gates, exit0. Example and documentation targets that select zero tests are not counted as backend proof. The public evidence README and source hashes distinguish the final working-tree observation from later evidence-only commits. + +Agent counts: unit49 executor17→22, unit50 executor22→38, unit51 runtime library66→74/package186; Eventlog focused176→199 and final complete persistence545. Agent tokens, tool counts and per-agent wall duration are unknown because this harness did not expose them. Final performance medians11.532148/13.049370/13.568977ms at55/601/1203events. Detailed evidence stays in the immutable records and recovery archives. + +The approved endpoint remains one PR from fix/github-issues-49-51 to main. Primary checkouts remain unchanged. Publication is followed by managed archive/finish/gc of only this wave's trees, preserving raw evidence privately. No main merge or release is authorized by this completion record. + +## PR delivery and CI parity + +PR52 is open at https://github.com/beyond10x/entity-runtime/pull/52 under b10x-bot[bot]. The reusable CI workflow carries its own step list; final delivery review added the same already-passing provider checker commands to its Rust1.91 job, with retained exact reports/generated suite/coverage. This closes local/CI gate drift. Source and Cargo hashes remain unchanged; no executable implementation changed. Follow-up common checks and GitHub checks judge the final workflow commit. Raw/private evidence remains outside published source. + +## Release authorization on 2026-10-03 + +The operator subsequently requested that PR #52 be merged and a new version cut. This supersedes the earlier PR-only delivery boundary. Task release-0-26-0 prepares the minor release on the existing integration branch; merge and publication are authorized. The SQLite change-tracking boundary remains exactly as accepted. Completion requires the bot-authored published release, its successful exact-tag workflow, and verified platform artifacts; documentation publication remains asynchronous. diff --git a/.engineering/planning/review-result/issue-49-adversary-public.md b/.engineering/planning/review-result/issue-49-adversary-public.md new file mode 100644 index 00000000..e4f6e6e4 --- /dev/null +++ b/.engineering/planning/review-result/issue-49-adversary-public.md @@ -0,0 +1,176 @@ +--- +format: aep.planning-md/3 +id: review-result:issue-49-adversary-public +kind: review-result +status: active +title: Issue 49 adversarial verification with machine paths redacted +relations: +- reviews: story:declared-refusal-before-fulfillment-validation +revision: 1 +--- +unit: story:declared-refusal-before-fulfillment-validation, issue-49-20261003 working tree over 72a9c36fbd58baee10be844a170649692772e51a +verdict: nothing found +cases: executed 20→22, red 0 +origin: introduced 0 / pre-existing 0 / undecided 0 +wrote-outside-worktree: none +needs-coordinator: run admitted ESS scenarios and integration gate + +Path redaction: machine-specific absolute checkout prefixes in compiler output are replaced with ; all other test output and findings are unchanged. + +1. git --no-pager diff --stat + +```text + crates/entity-executor/src/lib.rs | 20 ++- + .../tests/service_3_fulfillment_retry.rs | 135 ++++++++++++++++++++- + 2 files changed, 141 insertions(+), 14 deletions(-) +``` + +These tracked edits were inherited from the implementor; the captured inherited.patch remains byte-identical to git diff --binary after review (cmp exit 0). No production source was edited by this review. Reviewer additions are untracked and shown by git diff --no-index --stat /dev/null crates/entity-executor/tests/refusal_fulfillment_review.rs: + +```text + .../tests/refusal_fulfillment_review.rs | 144 +++++++++++++++++++++ + 1 file changed, 144 insertions(+) +``` + +2. Added cases, written before any runner execution + +File: crates/entity-executor/tests/refusal_fulfillment_review.rs. Both green; no red output was produced. + +- later_refusal_rolls_back_earlier_fulfillment_and_leaves_batch_identity_reusable: a successful first batch action creates the state that selects a refusal on the next action. The second action supplies an invalid array value and unknown removal key. Exact refusal outcome/error/message survives, repeated batch attempts leave state/history and both record identities untouched, and a corrected batch reuses its identity successfully. Exact retry returns the same receipt and only three history records exist. +- refusal_wins_over_required_field_removal_and_invalid_value_after_restart: required-field removal, null and object values all preserve the stored-field refusal after constructing a fresh executor; no state change or record appears. + +Individual runs occurred in this order before the package suite. Both commands used CARGO_BUILD_JOBS=2 CARGO_PROFILE_DEV_DEBUG=0 and --locked. + +Command: cargo test -p entity-executor --locked --test refusal_fulfillment_review later_refusal_rolls_back_earlier_fulfillment_and_leaves_batch_identity_reusable -- --exact +Exit: 0 +```text + Compiling entity-executor v0.25.1 (/crates/entity-executor) + Finished `test` profile [unoptimized] target(s) in 0.38s + Running tests/refusal_fulfillment_review.rs (target/debug/deps/refusal_fulfillment_review-30d16e3b6f2bfef3) + +running 1 test +test later_refusal_rolls_back_earlier_fulfillment_and_leaves_batch_identity_reusable ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.01s + +``` +Command: cargo test -p entity-executor --locked --test refusal_fulfillment_review refusal_wins_over_required_field_removal_and_invalid_value_after_restart -- --exact +Exit: 0 +```text + Finished `test` profile [unoptimized] target(s) in 0.03s + Running tests/refusal_fulfillment_review.rs (target/debug/deps/refusal_fulfillment_review-30d16e3b6f2bfef3) + +running 1 test +test refusal_wins_over_required_field_removal_and_invalid_value_after_restart ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s + +``` + +3. Package suite + +Before count 20 comes from the implementor report; no pre-addition suite was run by this reviewer. +Command: CARGO_BUILD_JOBS=2 CARGO_PROFILE_DEV_DEBUG=0 cargo test -p entity-executor --locked +Exit: 0 +```text + Finished `test` profile [unoptimized] target(s) in 0.06s + Running unittests src/lib.rs (target/debug/deps/entity_executor-8dfaba511738c0d0) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/async_recorded_contract.rs (target/debug/deps/async_recorded_contract-0ff004ea4fd67469) + +running 9 tests +test stale_observation_in_a_mixed_batch_rolls_back_every_record_and_receipt ... ok +test exact_imported_retry_is_historical_and_missing_matching_facts_are_not_success ... ok +test global_record_ids_duplicate_requests_and_empty_batches_are_closed_before_writes ... ok +test complete_decisions_keep_duplicate_events_and_observations_keep_explicit_null_provenance ... ok +test changed_expectation_or_provenance_conflicts_with_the_original_retry ... ok +test mixed_batches_use_ordered_local_state_and_observations_do_not_advance_revision ... ok +test retry_uses_saved_definition_and_verified_prefix_before_current_authority ... ok +test commit_then_uncertain_and_dropped_response_recover_one_effect_and_receipt ... ok +test named_and_single_retries_preserve_original_receipt_coordinates ... ok + +test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running tests/operation_fulfillment_review_1.rs (target/debug/deps/operation_fulfillment_review_1-591184104ce6323d) + +running 1 test +test an_older_service_retry_does_not_ignore_new_fulfillment_coordinates ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running tests/refusal_fulfillment_review.rs (target/debug/deps/refusal_fulfillment_review-30d16e3b6f2bfef3) + +running 2 tests +test refusal_wins_over_required_field_removal_and_invalid_value_after_restart ... ok +test later_refusal_rolls_back_earlier_fulfillment_and_leaves_batch_identity_reusable ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running tests/service_2_retry.rs (target/debug/deps/service_2_retry-3136ec45f310d2f2) + +running 1 test +test service_2_retry_distinguishes_absent_and_present_optional_arguments ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running tests/service_3_fulfillment_retry.rs (target/debug/deps/service_3_fulfillment_retry-e82cdeb516e5a052) + +running 5 tests +test subject_refusal_ignores_success_fulfillments ... ok +test refusal_ignores_success_fulfillments ... ok +test accepted_outcomes_still_require_exact_fulfillment_keys ... ok +test retry_restart_and_verified_history_preserve_set_and_removal_actions ... ok +test legacy_request_domains_compare_new_actions_in_single_and_batch_recovery ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s + + Running tests/service_binding_review_one.rs (target/debug/deps/service_binding_review_one-ad0275abebd220b6) + +running 1 test +test service_2_retry_replays_the_same_present_value_after_canonical_normalization ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running tests/service_branchless_retry.rs (target/debug/deps/service_branchless_retry-3559d39a28f19581) + +running 3 tests +test two_branchless_service_1_creations_in_one_batch_keep_their_own_requests ... ok +test a_branchless_service_1_retry_carrying_other_input_is_not_the_request_already_committed ... ok +test a_branchless_service_1_retry_reads_the_normalized_input_defaults_included ... ok + +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Doc-tests entity_executor + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + +``` + +cargo fmt --check also exited 0. Package clippy --all-targets --locked -- -D warnings exited 0 after the additions, with the same bounded build environment. + +4. Judgement findings + +Nothing found. Findings cover the working tree identified above. No conformance execution is claimed: new YAML documents were read in full and their expected refusal/state/receipt/history values compared with the executor implementation and tests; the integration coordinator owns their actual ESS admission and execution. + +5. Attacks that did not break + +Exact declared refusal precedence held against malformed and unknown fulfillment actions, including required-field removal. +Two-action batches with a successful first decision and later refusal left no partial write or consumed identity. +Corrected retry committed both actions and exact successful retry preserved its receipt and complete history length. +Existing accepted-outcome missing/extra-key tests and successful fulfillment/retry controls still executed and passed. +Caller review: decide_and_append delegates every Execute action to the changed decide branch; merge also delegates there. A separate fork/merge fixture was not exercised. +No kernel source, IO boundary, unsafe declaration, dependency set, public API, or ordering collection changed in this unit. + +6. Outside-worktree writes + +None. Scratch logs/report/inherited diff are under target/issue-49-scratch/review; compiler output is this checkout's target. The worktree CLI manages its mandatory lease registry outside the checkout; no outside files were manually written. + +```findings +[] +``` diff --git a/.engineering/planning/review-result/issue-50-adversary.md b/.engineering/planning/review-result/issue-50-adversary.md new file mode 100644 index 00000000..eb840257 --- /dev/null +++ b/.engineering/planning/review-result/issue-50-adversary.md @@ -0,0 +1,217 @@ +--- +format: aep.planning-md/3 +id: review-result:issue-50-adversary +kind: review-result +status: active +title: Independent review of explicit executor version binding +relations: +- reviews: story:executor-input-refusal-before-existence +revision: 1 +--- +unit: story:executor-input-refusal-before-existence, issue-50-20261003 working tree over c6164443df9f303f4898f1de1abfc1c899e8c921 +verdict: nothing found +cases: executed 35→38, red 0 +origin: introduced 0 / pre-existing 0 / undecided 0 +wrote-outside-worktree: none +needs-coordinator: execute adapter/ESS scenarios and integration gate + +1. git --no-pager diff --stat + +```text + checks/ess-conformance/src/executor.rs | 91 ++++++++-- + crates/entity-executor/src/lib.rs | 305 +++++++++++++++++++++++++++++---- + 2 files changed, 350 insertions(+), 46 deletions(-) +``` + +These tracked changes were inherited from the implementor. The inherited.patch captured before review is byte-identical to the tracked diff after review (cmp exited 0). This reviewer changed no production file and no pre-existing test. Reviewer additions are untracked and shown by git diff --no-index --stat /dev/null crates/entity-executor/tests/version_binding_review.rs: + +```text + .../tests/version_binding_review.rs | 184 +++++++++++++++++++++ + 1 file changed, 184 insertions(+) +``` + +Path redaction: machine-specific absolute checkout prefixes in compiler output below are replaced with ; no other output or findings are transformed. Private raw logs remain in assigned scratch. + +2. New cases, written before any execution + +All three are in crates/entity-executor/tests/version_binding_review.rs. All green; no red output was produced. Each case ran alone before the package suite. + +- divergent_registered_versions_select_only_the_requested_guard uses versions 1 and 2 with opposite refusal predicates and distinct outcome/error identifiers. Each exact version returns its own refusal without a load; unregistered version 3 returns EntityNotRegistered with version 3 and performs no load. +- explicit_and_row_bound_execution_persist_identical_bytes_and_replay_across_apis compares actual records written to two real stores by legacy and explicit APIs for both versions. Request bytes, complete entry and receipt are identical; retries cross API boundaries with an empty registry and preserve receipts; changed version is RecordConflict. +- accepted_wrong_version_in_local_batch_rolls_back_and_releases_all_identities creates version 2 locally then executes an accepted version-1 operation, requiring EntityMismatch. Neither record, batch receipt nor row is saved. Correcting version commits both actions and a registry-free retry returns the same receipt. + +All commands below use CARGO_PROFILE_DEV_DEBUG=0 CARGO_BUILD_JOBS=2. +Command: cargo test -p entity-executor --locked --test version_binding_review divergent_registered_versions_select_only_the_requested_guard -- --exact +Exit: 0 +```text + Compiling entity-executor v0.25.1 (/crates/entity-executor) + Finished `test` profile [unoptimized] target(s) in 0.50s + Running tests/version_binding_review.rs (target/debug/deps/version_binding_review-42d95fd63a5cad46) + +running 1 test +test divergent_registered_versions_select_only_the_requested_guard ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s + +``` +Command: cargo test -p entity-executor --locked --test version_binding_review explicit_and_row_bound_execution_persist_identical_bytes_and_replay_across_apis -- --exact +Exit: 0 +```text + Finished `test` profile [unoptimized] target(s) in 0.06s + Running tests/version_binding_review.rs (target/debug/deps/version_binding_review-42d95fd63a5cad46) + +running 1 test +test explicit_and_row_bound_execution_persist_identical_bytes_and_replay_across_apis ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.01s + +``` +Command: cargo test -p entity-executor --locked --test version_binding_review accepted_wrong_version_in_local_batch_rolls_back_and_releases_all_identities -- --exact +Exit: 0 +```text + Finished `test` profile [unoptimized] target(s) in 0.04s + Running tests/version_binding_review.rs (target/debug/deps/version_binding_review-42d95fd63a5cad46) + +running 1 test +test accepted_wrong_version_in_local_batch_rolls_back_and_releases_all_identities ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s + +``` + +3. Package suite + +Before count 35 comes from the implementor's report; this reviewer ran no suite before adding the probes. The resulting count is 38. All existing lanes retain their counts. +Command: CARGO_PROFILE_DEV_DEBUG=0 CARGO_BUILD_JOBS=2 cargo test -p entity-executor --locked +Exit: 0 +```text + Finished `test` profile [unoptimized] target(s) in 0.06s + Running unittests src/lib.rs (target/debug/deps/entity_executor-8dfaba511738c0d0) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/async_recorded_contract.rs (target/debug/deps/async_recorded_contract-0ff004ea4fd67469) + +running 9 tests +test exact_imported_retry_is_historical_and_missing_matching_facts_are_not_success ... ok +test stale_observation_in_a_mixed_batch_rolls_back_every_record_and_receipt ... ok +test complete_decisions_keep_duplicate_events_and_observations_keep_explicit_null_provenance ... ok +test global_record_ids_duplicate_requests_and_empty_batches_are_closed_before_writes ... ok +test changed_expectation_or_provenance_conflicts_with_the_original_retry ... ok +test commit_then_uncertain_and_dropped_response_recover_one_effect_and_receipt ... ok +test mixed_batches_use_ordered_local_state_and_observations_do_not_advance_revision ... ok +test retry_uses_saved_definition_and_verified_prefix_before_current_authority ... ok +test named_and_single_retries_preserve_original_receipt_coordinates ... ok + +test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running tests/input_refusal_precedence.rs (target/debug/deps/input_refusal_precedence-f78a1ca7f7ecaaf6) + +running 13 tests +test executor_input_refusal_missing_execute ... ok +test recorded_refusal_keeps_explicit_definition_authority ... ok +test versioned_empty_and_invalid_shape_requests_perform_no_io ... ok +test versioned_merge_refusal_precedes_the_merge_history_read ... ok +test imported_execution_retry_checks_explicit_version_before_accepting_identity ... ok +test executor_input_refusal_existing_create ... ok +test versioned_refusal_precedes_existing_revision_and_definition_mismatch ... ok +test accepted_input_preserves_absence_revision_and_definition_checks ... ok +test versioned_batch_refusal_rolls_back_earlier_local_decisions ... ok +test legacy_committed_execution_can_be_retried_with_its_explicit_saved_version ... ok +test legacy_execute_keeps_row_authority_without_choosing_a_registered_version ... ok +test explicit_definition_does_not_require_registry_for_committed_retry ... ok +test named_mixed_batch_retry_uses_saved_versions_and_rejects_changed_binding ... ok + +test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running tests/operation_fulfillment_review_1.rs (target/debug/deps/operation_fulfillment_review_1-591184104ce6323d) + +running 1 test +test an_older_service_retry_does_not_ignore_new_fulfillment_coordinates ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/refusal_fulfillment_review.rs (target/debug/deps/refusal_fulfillment_review-30d16e3b6f2bfef3) + +running 2 tests +test refusal_wins_over_required_field_removal_and_invalid_value_after_restart ... ok +test later_refusal_rolls_back_earlier_fulfillment_and_leaves_batch_identity_reusable ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running tests/service_2_retry.rs (target/debug/deps/service_2_retry-3136ec45f310d2f2) + +running 1 test +test service_2_retry_distinguishes_absent_and_present_optional_arguments ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/service_3_fulfillment_retry.rs (target/debug/deps/service_3_fulfillment_retry-e82cdeb516e5a052) + +running 5 tests +test refusal_ignores_success_fulfillments ... ok +test subject_refusal_ignores_success_fulfillments ... ok +test accepted_outcomes_still_require_exact_fulfillment_keys ... ok +test retry_restart_and_verified_history_preserve_set_and_removal_actions ... ok +test legacy_request_domains_compare_new_actions_in_single_and_batch_recovery ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s + + Running tests/service_binding_review_one.rs (target/debug/deps/service_binding_review_one-ad0275abebd220b6) + +running 1 test +test service_2_retry_replays_the_same_present_value_after_canonical_normalization ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/service_branchless_retry.rs (target/debug/deps/service_branchless_retry-3559d39a28f19581) + +running 3 tests +test a_branchless_service_1_retry_carrying_other_input_is_not_the_request_already_committed ... ok +test a_branchless_service_1_retry_reads_the_normalized_input_defaults_included ... ok +test two_branchless_service_1_creations_in_one_batch_keep_their_own_requests ... ok + +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/version_binding_review.rs (target/debug/deps/version_binding_review-42d95fd63a5cad46) + +running 3 tests +test divergent_registered_versions_select_only_the_requested_guard ... ok +test accepted_wrong_version_in_local_batch_rolls_back_and_releases_all_identities ... ok +test explicit_and_row_bound_execution_persist_identical_bytes_and_replay_across_apis ... ok + +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Doc-tests entity_executor + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + +``` + +Package clippy --all-targets --locked -- -D warnings exited 0, with the same bounded build environment. cargo fmt --all -- --check and git diff --check exited 0 with empty output. + +4. Judgement findings + +Nothing found. Findings cover the working tree identified above. Full production and adapter diff, all new tests, all five new ESS scenario documents, acceptance, and callers of the changed execution/recovery paths were read. Adapter and ESS execution were not run in this package-only review and remain with the coordinator. + +5. Attacks that did not break + +Divergent version definitions preserved exact requested authority, including lower registered and absent versions. +Direct legacy-versus-explicit execution comparison preserved canonical request bytes, complete decision entry and receipt for versions 1 and 2. +Accepted wrong-version batch execution preserved atomicity and did not consume record or batch identities. +The existing committed and imported retry cases ran and preserved exact version matching with an empty registry; changed versions conflicted and missing imported definitions remained unverifiable. +The existing input-refusal tests ran and pinned trace absence of state load or merge-history read, while creation-over-existing, malformed shape, accepted absence/revision conflicts and recorded refusal version behavior remained covered. +The adapter checks presence before typed deserialization: null/string versions cannot fall back silently; batch execute/merge members in versioned mode require explicit versions, and create/observe members reject an extraneous definition_version. This is source-level inspection only. +The new merge scenario asserts the selected base and both branch tips at a capturing port that refuses append. It makes no durable-merge claim. Separate durable merge execution was not exercised by this reviewer. + +6. Outside-worktree writes + +None. Scratch logs/report/inherited diff are under target/issue-50-scratch/review; compiler output is this checkout's target. The worktree CLI maintains its mandatory lease registry; no outside files were manually written. + +```findings +[] +``` diff --git a/.engineering/planning/review-result/issue-51-continuity-design.md b/.engineering/planning/review-result/issue-51-continuity-design.md new file mode 100644 index 00000000..59c1cf8c --- /dev/null +++ b/.engineering/planning/review-result/issue-51-continuity-design.md @@ -0,0 +1,70 @@ +--- +format: aep.planning-md/3 +id: review-result:issue-51-continuity-design +kind: review-result +status: active +title: Independent review of the refined capture continuity design +relations: +- reviews: story:bounded-batch-and-facade-reads +revision: 1 +--- +unit: GitHub issue #51 refined continuity design; ER working tree f57bf975 plus proposed Eventlog capture extension +verdict: nothing found; implementation safety unproven in this design-only pass +cases: executed 0→0, red 0 +origin: introduced 0 / pre-existing 0 / undecided 0 +wrote-outside-worktree: none +needs-coordinator: preserve the refinements below in normative prose and verify their executable controls + +1. git --no-pager diff --stat + +```text + crates/entity-eventlog/src/adapter.rs | 42 +++++++++++++++++- + crates/entity-eventlog/src/adapter/scoped.rs | 15 +++++-- + crates/entity-eventlog/src/facade.rs | 62 +++++++++++++++++++++++++- + crates/entity-eventlog/src/lib.rs | 4 +- + crates/entity-eventlog/src/sync.rs | 65 ++++++++++++++++++++++++---- + 5 files changed, 172 insertions(+), 16 deletions(-) +``` + +This is the implementor's in-progress shared-tree diff, not this review's changes. The reviewer wrote only this ignored report and managed its own worktree lease. No source, test, specification or planning file was edited. The previously authored performance test is outside this read-only subtask. + +2. Cases added + +None, as explicitly assigned. No test or build was executed. This is a bounded design pass, not verification of unfinished implementation. + +3. Scope and authority + +Read Eventlog's proposed docs/design/consistent-tenant-capture.md:263-464 and additive crates/eventlog-core/src/capture.rs checkpoint/update types and default trait method. Read ER target/issue-51-scratch/runtime-design.md:3-17. Apply the coordinator's additional issuance restriction: SQLite returns no checkpoint when cold schema verification sees SQL triggers or foreign-key cascades; complete fallback remains available. The operator accepts SQL-visible change tracking, mandatory full verification on open/reopen, and exclusion of raw database/WAL file modification outside SQLite from warm continuity guarantees. + +4. Findings + +No unresolved design counterexample found under those refined assumptions. Implementation correctness is unproven: the safety fact reached source/document reasoning, not an executed conformance case in this pass. This is not approval and supplies no signed verification evidence. + +Two clarifications were relayed while the implementation is still being written: + +- ER runtime-design.md:11 must require both directions of materialization agreement. Checking only rows returned in a delta would miss an omitted required record/batch/subject row. The runtime author confirmed the intended design computes every expected changed row, checks all supplied before/after values, and checks that every expected final row exists. Unrelated before==after rows may be retained as harmless accounting; unrelated new values may not enter the verified cache. The author reports the implementation follows this rule, which this design pass has not executed. +- Eventlog consistent-tenant-capture.md:372-405 predates the latest no-trigger/no-cascade refinement. The provider author was asked to state the issuance restriction and cold re-establishment requirement explicitly. The report evaluates the refined contract supplied by the coordinator; it does not treat unfinished prose as a finished production defect. + +5. Defensive reasoning and required executable controls + +Opaque authority: capture.rs:47-59 permits extension providers through Any, while the SQLite payload must remain private and carry the exact Arc issuer identity. Cloning the issued capability preserves authority; arbitrary new payloads, foreign connections, changed tenant/specification/limit tuples and reopened issuers cannot satisfy that private payload/scope check. A real foreign-token/scope test remains required; public generic construction alone is not a forgery. + +Cumulative limits: design:343-345 and :424-429 bind exact limits and complete usage, requiring checked additions and before/after row-size adjustments. Limits over the delta alone would admit a complete observation beyond the caller's cap. Controls must independently exceed each whole-capture cap after individually small appends and include orphan blobs, deletion/reinsertion, present null and returned-to-base rows. + +Projection completeness: design:394-396 and :431-436 require every provider-controlled mutation's original/final values, then consumer validation. The clarified exact expected-row check is necessary to reject omissions, not merely unexpected additions. Controls must omit one record, batch and subject delta in turn and compare tracked versus full refusal. + +Concurrent stamp windows: design:377-405 obtains the external stamp inside BEGIN IMMEDIATE and retains that pre-commit external stamp when publishing journal state under the connection mutex. A stamp freshly read after COMMIT could absorb a rival SQL commit and hide it. A deterministic post-COMMIT/pre-publication interleaving must demonstrate full fallback; wall-clock concurrency loops alone do not prove that window was reached. + +Unaccounted writes: design:391-412 restricts continuity to acknowledged instrumented groups and invalidates after failed/uncertain operations or other provider paths. Repeated blob bindings must not increase usage; actual new orphan bindings must. Same-connection total changes, external data-version changes and schema changes have distinct jobs. The no-trigger/no-cascade issuance restriction prevents indirect SQLite writes from escaping interface-level mutation collection. SQL event/blob/row rewrites preserving head/count remain mandatory refusal controls after fallback. + +Consumer prefix proof: runtime-design.md:9-13 retains the verified base, generation and token as one cache authority. Only proven unchanged prefixes admit linear suffix replay. Full new batch membership, exact positions, fresh global identities, predecessor replay and exact projection updates are necessary before the token advances. Failure must discard the tentative advance; a racing generation must retry. Imports, lineage and unsupported changes must go through complete verification. + +Existing limitations distinguished from new claims: design:439-443 expressly does not promise an append precondition covering all history between a verified read and a later write. A rival corruption may therefore race an acknowledged write, but cannot yield silently verified cached success afterward. Complete snapshot/history outputs necessarily clone returned data; the flat-cost claim must isolate fixed-size batches and unchanged verification from output size. Raw file bypass is the accepted warm-policy exclusion, not a newly discovered flaw. Default FullVerification callers retain their existing guarantee. + +6. Outside-worktree writes + +None. The only review artifact is target/issue-51-scratch/design-review/report.md. Worktree lifecycle metadata is maintained by the mandated CLI. No build, external integration, planning write or commit occurred. + +```findings +[] +``` diff --git a/.engineering/planning/review-result/issue-51-provider-checker.md b/.engineering/planning/review-result/issue-51-provider-checker.md new file mode 100644 index 00000000..9612d118 --- /dev/null +++ b/.engineering/planning/review-result/issue-51-provider-checker.md @@ -0,0 +1,44 @@ +--- +format: aep.planning-md/3 +id: review-result:issue-51-provider-checker +kind: review-result +status: active +title: Real provider conformance and negative controls +relations: +- reviews: story:bounded-batch-and-facade-reads +revision: 1 +--- +Provider conformance checker verification — issue 51 + +The eventlog-enabled checker compiled without adapter or compiler changes. The separate provider contract was generated with the explicit coverage-review reason: “Issue 51: admit seven real SQLite and public facade scenarios for exact state/history semantics and five SQL corruptions with unchanged event heads; retain the original 421-scenario contract unchanged.” + +Commands used Rust 1.91, CARGO_BUILD_JOBS=2, CARGO_PROFILE_DEV_DEBUG=0 and the temporary `target/issue-wave-scratch/provider-paths.toml` patch to the five local Eventlog crates. Build command: `cargo +1.91.0 --config target/issue-wave-scratch/provider-paths.toml build --manifest-path checks/ess-conformance/Cargo.toml --features eventlog`. First build exited 0 in 37.74 seconds. Checker formatting check exited 0. No executable or test source was changed in this task. + +`er-ess-conformance --spec-root ess/provider-tracking check` ran three times with identical report-document counts: total 17, passed 17, failed 0, error 0, unsupported 0, skipped 0. Each report declares complete coverage: seven authored scenarios and ten generated command-response scenarios. The generated command-response cases are not represented as extra semantic coverage. + +The seven authored scenarios executed: + +- multi-subject-histories-one-scope: actual history order, duplicated subjects, missing subject, exact IDs/revisions, Closed state and revision 2 after a multi-subject batch; full-reopen comparison. +- unchanged-capture-retains-verified-state: exact records, revisions and Closed state before and after repeated snapshots and full reopen. +- unchanged-head-blob-tamper: one SQL row changed, event head unchanged, warm read and reopen refuse ProviderIntegrity. +- unchanged-head-delete-blob-tamper: one blob deleted, event head unchanged, warm read and reopen refuse ProviderIntegrity. +- unchanged-head-event-tamper: one event changed, event head unchanged, warm read and reopen refuse ProviderIntegrity. +- unchanged-head-identity-tamper: identity changed, event head unchanged, warm read and reopen refuse ProviderIntegrity. +- unchanged-head-projection-tamper: projection changed, event head unchanged, warm read and reopen refuse ProviderIntegrity. + +The primary provider report is `target/provider-ess-conformance/run.json`. Repeated reports are in this scratch directory under `repeat-2` and `repeat-3`. The original five-library contract was also executed with the eventlog-enabled checker: 421 passed, no failures/errors/skips. SHA-256 checks proved `ess/coverage.json`, `ess/generated/model.json` and `ess/generated/suite.json` remained byte-for-byte unchanged. + +Negative controls were made only in scratch copies of the provider specification, leaving the actual semantic assertions intact: + +1. Keep the provider domain and commands but remove CapturePolicy. Regeneration exits 1 with `provider specification must declare CapturePolicy`. +2. Change only the expected returned lifecycle state from Closed to Open in `unchanged-capture-retains-verified-state`; keep the actual definition transition and commands unchanged. The real provider still returns Closed. The run exits 1, with 16 passed and exactly that authored scenario failed. Both Load checks report `ESS-CF-PAYLOAD`: `response field value differs from its declared literal`. This is an assertion negative control, not a production-source mutation claim. + +New generated files are `ess/provider-tracking/coverage.json`, `ess/provider-tracking/generated/model.json` and `ess/provider-tracking/generated/suite.json`. No AEP or runtime file was edited. + +The checker lock initially lacked the optional provider dependency graph. The local path build added 413 lines and removed one. Its exact provisional diff is retained as `local-lock.patch`, with added package names in `added-packages.txt`; additions include entity-eventlog, eventlog-core, eventlog-sqlite, rusqlite 0.40.2, libsqlite3-sys, time, tokio and their transitive dependencies. No new manifest dependency was needed. The checker lock was restored from its pre-build copy, and the workspace lock remained byte-for-byte unchanged. Final Git pins and lock regeneration belong to the coordinator. + +Provider source fingerprints matched before and after checks. Original build logs contain private absolute checkout paths and remain scratch-only; this report contains no personal paths. The coordinator subsequently released runtime source for test-wrapper changes, so these results identify the pre-wrapper, locally patched binary recorded in `executable.sha256`, not a final published-pin gate. The pinned build and repository final checks must be rerun after those changes. No CI-image execution was performed in this bounded task. + +```findings +[] +``` diff --git a/.engineering/planning/review-result/issue-51-runtime-adversary.md b/.engineering/planning/review-result/issue-51-runtime-adversary.md new file mode 100644 index 00000000..b264d246 --- /dev/null +++ b/.engineering/planning/review-result/issue-51-runtime-adversary.md @@ -0,0 +1,486 @@ +--- +format: aep.planning-md/3 +id: review-result:issue-51-runtime-adversary +kind: review-result +status: active +title: Incremental receipt parity regression and resolved recheck +relations: +- reviews: story:bounded-batch-and-facade-reads +revision: 1 +--- +unit: #51 incremental runtime working tree based on f57bf9753abec7aaa5ff9103bd9b66e29f3398cd, before receipt fix +verdict: CONFIRMED (resolved by implementor and rechecked below) +cases: executed 183→186, red 2 before fix / 0 after fix +origin: introduced 1 / pre-existing 0 / undecided 0 +wrote-outside-worktree: none +needs-coordinator: retain defect and its resolved follow-up separately; no outstanding runtime defect from this pass + +```text +git --no-pager diff --stat + crates/entity-eventlog/src/adapter.rs | 52 +++++++++++- + crates/entity-eventlog/src/adapter/scoped.rs | 15 +++- + crates/entity-eventlog/src/facade.rs | 121 ++++++++++++++++++++++++++- + crates/entity-eventlog/src/lib.rs | 6 +- + crates/entity-eventlog/src/sync.rs | 121 +++++++++++++++++++++++++-- + 5 files changed, 298 insertions(+), 17 deletions(-) +``` + +The working-tree stat includes inherited implementation changes and omits untracked files. Reviewer additions are the following test file plus two lines declaring its test-only module in `adapter/tracked.rs`. The coordinator explicitly authorized that exact module declaration; the reviewer made no other production edits. The later receipt guard was written by the implementor, not this review. No existing test was changed. The earlier performance test was frozen throughout this review. + +```text + .../src/adapter/tracked/review_tests.rs | 419 +++++++++++++++++++++ + 1 file changed, 419 insertions(+) +``` + +Cases were written before execution. The first suite run followed the isolated red cases and the implementor's fix; no claim of a pre-fix whole-suite run is made. Baseline 183 comes from the implementor's reported all-feature package result (71 library tests). This review adds three library tests, giving 74 library tests and 186 total executed tests. The separately reported performance probe remains ignored in this package invocation. + +Public-path redaction: only machine-specific absolute checkout prefixes in captured output are replaced with `` and ``. Original private logs remain in this scratch directory. Earlier compile-only import errors in the second test were corrected before measuring it; they are not findings. + +1. `incremental_receipts_refuse_a_single_record_key_that_names_another_record` constructs a complete and internally consistent delta with a mismatched SingleRecord receipt. Full verification refuses it, while the original incremental verifier accepted it. This lower-level fabricated-provider shape alone does not establish default-projector reachability; it protects the remaining receipt-coordinate class. It is GREEN after the implementor fix. + +Command: `CARGO_BUILD_JOBS=2 CARGO_PROFILE_DEV_DEBUG=0 cargo +1.91.0 --config target/issue-51-scratch/paths.toml test -p entity-eventlog --all-features --lib incremental_receipts_refuse_a_single_record_key_that_names_another_record -- --nocapture` + +Original isolated output (exit 101): + +```text + Locking 5 packages to latest compatible versions + Adding eventlog-core v0.6.0 (/crates/eventlog-core) + Adding eventlog-file v0.6.0 (/crates/eventlog-file) + Adding eventlog-postgres v0.6.0 (/crates/eventlog-postgres) + Adding eventlog-sqlite v0.6.0 (/crates/eventlog-sqlite) + Adding eventlog-tree v0.6.0 (/crates/eventlog-tree) + Compiling entity-eventlog v0.25.1 (/crates/entity-eventlog) + Finished `test` profile [unoptimized] target(s) in 3.55s + Running unittests src/lib.rs (target/debug/deps/entity_eventlog-e345c4868818aed2) + +running 1 test + +thread 'adapter::tracked::review_tests::incremental_receipts_refuse_a_single_record_key_that_names_another_record' (310046) panicked at crates/entity-eventlog/src/adapter/tracked/review_tests.rs:269:5: +incremental verifier accepted a single-record receipt that the full verifier refuses +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +test adapter::tracked::review_tests::incremental_receipts_refuse_a_single_record_key_that_names_another_record ... FAILED + +failures: + +failures: + adapter::tracked::review_tests::incremental_receipts_refuse_a_single_record_key_that_names_another_record + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 72 filtered out; finished in 0.03s + +error: test failed, to rerun pass `-p entity-eventlog --lib` +``` + +2. `acknowledged_native_append_with_blank_named_key_is_refused_before_tracked_reuse` uses the real SQLite backend and default ER projector. After a validated creation, the caller builds a canonically encoded decision/request and coherent wrapper/blob/batch set whose named batch key is empty. Public `append_group_guarded_with_blobs` with `NoGuard` acknowledges it. An independent full capture returns `CorruptHistory` naming the invalid key; the warm tracked lookup originally returned the malformed committed receipt. This is the reachable integrity regression, and it is GREEN after the fix. It requires a native Eventlog writer; the normal ER executor validates keys before append. + +Command: `CARGO_BUILD_JOBS=2 CARGO_PROFILE_DEV_DEBUG=0 cargo +1.91.0 --config target/issue-51-scratch/paths.toml test -p entity-eventlog --all-features --lib acknowledged_native_append_with_blank_named_key_is_refused_before_tracked_reuse --locked -- --nocapture` + +Original isolated output (exit 101): + +```text + Compiling entity-eventlog v0.25.1 (/crates/entity-eventlog) + Finished `test` profile [unoptimized] target(s) in 3.64s + Running unittests src/lib.rs (target/debug/deps/entity_eventlog-e345c4868818aed2) + +running 1 test + +thread 'adapter::tracked::review_tests::acknowledged_native_append_with_blank_named_key_is_refused_before_tracked_reuse' (412801) panicked at crates/entity-eventlog/src/adapter/tracked/review_tests.rs:415:5: +tracked read accepted malformed committed receipt: Ok(Some(Committed(StoredRecord { entry: Decision(RecordedCommit { instance: EntityInstance { entity: "ticket", version: 1, id: "clock", lifecycle_state: "Open", revision: 2, fields: {} }, envelope: Envelope { record_id: "bad-key-record", recorded_at: "2026-10-03T00:00:00Z", correlation: None, causation: None, actor: None, record: DecisionRecord { definition: Some(EntityDefinition { entity: "ticket", version: 1, schema: ObjectSchema { fields: {}, additional_fields: false }, lifecycle: LifecycleDefinition { initial: "Open", states: ["Open"] }, invariants: [], create: CreateDefinition { emit: None, arguments: ObjectSchema { fields: {}, additional_fields: false }, response: ObjectSchema { fields: {}, additional_fields: false }, outcomes: [] }, operations: {"Touch": OperationDefinition { arguments: ObjectSchema { fields: {}, additional_fields: false }, transitions: [TransitionDefinition { from: One("Open"), to: "Open" }], preconditions: [], set: {}, emits: [], response: ObjectSchema { fields: {}, additional_fields: false }, outcomes: [] }}, projections: {}, semantics: Kernel1, identity: None, relations: {}, scales: {}, number_observation: SourceNumber1 }), command: Execute { operation: "Touch", arguments: {}, fulfillments: {} }, entity: "ticket", id: "clock", revision: 2, from_state: Some("Open"), to_state: "Open", result: EntityInstance { entity: "ticket", version: 1, id: "clock", lifecycle_state: "Open", revision: 2, fields: {} }, changed: {}, removed: {}, events: [], outcome: None, effect: None, response: None } } }), position: RecordPosition { subject: 2, store: 3 }, receipt: RecordReceipt { record_id: "bad-key-record", subject: Subject { entity: "ticket", id: "clock" }, kind: Decision, revision: 2, position: RecordPosition { subject: 2, store: 3 }, batch_key: Named(""), member_index: 0 }, expect: Revision(1), request_bytes: [91, 34, 101, 114, 46, 114, 101, 113, 117, 101, 115, 116, 47, 49, 34, 44, 123, 34, 97, 114, 103, 117, 109, 101, 110, 116, 115, 34, 58, 123, 125, 44, 34, 101, 120, 112, 101, 99, 116, 101, 100, 95, 114, 101, 118, 105, 115, 105, 111, 110, 34, 58, 49, 44, 34, 107, 105, 110, 100, 34, 58, 34, 101, 120, 101, 99, 117, 116, 101, 34, 44, 34, 111, 112, 101, 114, 97, 116, 105, 111, 110, 34, 58, 34, 84, 111, 117, 99, 104, 34, 44, 34, 114, 101, 99, 111, 114, 100, 105, 110, 103, 34, 58, 123, 34, 97, 99, 116, 111, 114, 34, 58, 110, 117, 108, 108, 44, 34, 99, 97, 117, 115, 97, 116, 105, 111, 110, 34, 58, 110, 117, 108, 108, 44, 34, 99, 111, 114, 114, 101, 108, 97, 116, 105, 111, 110, 34, 58, 110, 117, 108, 108, 44, 34, 114, 101, 99, 111, 114, 100, 95, 105, 100, 34, 58, 34, 98, 97, 100, 45, 107, 101, 121, 45, 114, 101, 99, 111, 114, 100, 34, 44, 34, 114, 101, 99, 111, 114, 100, 101, 100, 95, 97, 116, 34, 58, 34, 50, 48, 50, 54, 45, 49, 48, 45, 48, 51, 84, 48, 48, 58, 48, 48, 58, 48, 48, 90, 34, 125, 44, 34, 115, 117, 98, 106, 101, 99, 116, 34, 58, 91, 34, 116, 105, 99, 107, 101, 116, 34, 44, 34, 99, 108, 111, 99, 107, 34, 93, 125, 93], lineage: None, record_bytes: [91, 34, 101, 114, 46, 114, 101, 99, 111, 114, 100, 47, 49, 34, 44, 123, 34, 99, 111, 109, 109, 105, 116, 34, 58, 123, 34, 101, 110, 118, 101, 108, 111, 112, 101, 34, 58, 123, 34, 97, 99, 116, 111, 114, 34, 58, 110, 117, 108, 108, 44, 34, 99, 97, 117, 115, 97, 116, 105, 111, 110, 34, 58, 110, 117, 108, 108, 44, 34, 99, 111, 114, 114, 101, 108, 97, 116, 105, 111, 110, 34, 58, 110, 117, 108, 108, 44, 34, 114, 101, 99, 111, 114, 100, 34, 58, 123, 34, 99, 104, 97, 110, 103, 101, 100, 34, 58, 123, 125, 44, 34, 99, 111, 109, 109, 97, 110, 100, 34, 58, 123, 34, 97, 114, 103, 117, 109, 101, 110, 116, 115, 34, 58, 123, 125, 44, 34, 99, 111, 109, 109, 97, 110, 100, 34, 58, 34, 101, 120, 101, 99, 117, 116, 101, 34, 44, 34, 111, 112, 101, 114, 97, 116, 105, 111, 110, 34, 58, 34, 84, 111, 117, 99, 104, 34, 125, 44, 34, 100, 101, 102, 105, 110, 105, 116, 105, 111, 110, 34, 58, 123, 34, 99, 114, 101, 97, 116, 101, 34, 58, 123, 34, 101, 109, 105, 116, 34, 58, 110, 117, 108, 108, 125, 44, 34, 101, 110, 116, 105, 116, 121, 34, 58, 34, 116, 105, 99, 107, 101, 116, 34, 44, 34, 105, 110, 118, 97, 114, 105, 97, 110, 116, 115, 34, 58, 91, 93, 44, 34, 108, 105, 102, 101, 99, 121, 99, 108, 101, 34, 58, 123, 34, 105, 110, 105, 116, 105, 97, 108, 34, 58, 34, 79, 112, 101, 110, 34, 44, 34, 115, 116, 97, 116, 101, 115, 34, 58, 91, 34, 79, 112, 101, 110, 34, 93, 125, 44, 34, 111, 112, 101, 114, 97, 116, 105, 111, 110, 115, 34, 58, 123, 34, 84, 111, 117, 99, 104, 34, 58, 123, 34, 97, 114, 103, 117, 109, 101, 110, 116, 115, 34, 58, 123, 34, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 95, 102, 105, 101, 108, 100, 115, 34, 58, 102, 97, 108, 115, 101, 44, 34, 102, 105, 101, 108, 100, 115, 34, 58, 123, 125, 125, 44, 34, 101, 109, 105, 116, 115, 34, 58, 91, 93, 44, 34, 112, 114, 101, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 34, 58, 91, 93, 44, 34, 115, 101, 116, 34, 58, 123, 125, 44, 34, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 115, 34, 58, 91, 123, 34, 102, 114, 111, 109, 34, 58, 34, 79, 112, 101, 110, 34, 44, 34, 116, 111, 34, 58, 34, 79, 112, 101, 110, 34, 125, 93, 125, 125, 44, 34, 112, 114, 111, 106, 101, 99, 116, 105, 111, 110, 115, 34, 58, 123, 125, 44, 34, 115, 99, 104, 101, 109, 97, 34, 58, 123, 34, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 95, 102, 105, 101, 108, 100, 115, 34, 58, 102, 97, 108, 115, 101, 44, 34, 102, 105, 101, 108, 100, 115, 34, 58, 123, 125, 125, 44, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 125, 44, 34, 101, 110, 116, 105, 116, 121, 34, 58, 34, 116, 105, 99, 107, 101, 116, 34, 44, 34, 101, 118, 101, 110, 116, 115, 34, 58, 91, 93, 44, 34, 102, 114, 111, 109, 95, 115, 116, 97, 116, 101, 34, 58, 34, 79, 112, 101, 110, 34, 44, 34, 105, 100, 34, 58, 34, 99, 108, 111, 99, 107, 34, 44, 34, 114, 101, 115, 117, 108, 116, 34, 58, 123, 34, 101, 110, 116, 105, 116, 121, 34, 58, 34, 116, 105, 99, 107, 101, 116, 34, 44, 34, 102, 105, 101, 108, 100, 115, 34, 58, 123, 125, 44, 34, 105, 100, 34, 58, 34, 99, 108, 111, 99, 107, 34, 44, 34, 108, 105, 102, 101, 99, 121, 99, 108, 101, 95, 115, 116, 97, 116, 101, 34, 58, 34, 79, 112, 101, 110, 34, 44, 34, 114, 101, 118, 105, 115, 105, 111, 110, 34, 58, 50, 44, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 125, 44, 34, 114, 101, 118, 105, 115, 105, 111, 110, 34, 58, 50, 44, 34, 116, 111, 95, 115, 116, 97, 116, 101, 34, 58, 34, 79, 112, 101, 110, 34, 125, 44, 34, 114, 101, 99, 111, 114, 100, 95, 105, 100, 34, 58, 34, 98, 97, 100, 45, 107, 101, 121, 45, 114, 101, 99, 111, 114, 100, 34, 44, 34, 114, 101, 99, 111, 114, 100, 101, 100, 95, 97, 116, 34, 58, 34, 50, 48, 50, 54, 45, 49, 48, 45, 48, 51, 84, 48, 48, 58, 48, 48, 58, 48, 48, 90, 34, 125, 44, 34, 105, 110, 115, 116, 97, 110, 99, 101, 34, 58, 123, 34, 101, 110, 116, 105, 116, 121, 34, 58, 34, 116, 105, 99, 107, 101, 116, 34, 44, 34, 102, 105, 101, 108, 100, 115, 34, 58, 123, 125, 44, 34, 105, 100, 34, 58, 34, 99, 108, 111, 99, 107, 34, 44, 34, 108, 105, 102, 101, 99, 121, 99, 108, 101, 95, 115, 116, 97, 116, 101, 34, 58, 34, 79, 112, 101, 110, 34, 44, 34, 114, 101, 118, 105, 115, 105, 111, 110, 34, 58, 50, 44, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 125, 125, 44, 34, 107, 105, 110, 100, 34, 58, 34, 100, 101, 99, 105, 115, 105, 111, 110, 34, 125, 93] }))) +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +test adapter::tracked::review_tests::acknowledged_native_append_with_blank_named_key_is_refused_before_tracked_reuse ... FAILED + +failures: + +failures: + adapter::tracked::review_tests::acknowledged_native_append_with_blank_named_key_is_refused_before_tracked_reuse + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 73 filtered out; finished in 0.02s + +error: test failed, to rerun pass `-p entity-eventlog --lib` +``` + +3. `coalesced_reverse_named_batches_keep_observation_heads_and_latest_state_source` appends real batches in reverse lexical key order, mixing transitions and observations. It compares terminal state, complete histories, records, batches and exact projection rows with full verification; it also removes a member event and requires refusal of the incomplete newly acknowledged batch. Its isolated first run was GREEN, as is the post-fix run. + +```text + Finished `test` profile [unoptimized] target(s) in 0.09s + Running unittests src/lib.rs (target/debug/deps/entity_eventlog-e345c4868818aed2) + +running 1 test +test adapter::tracked::review_tests::coalesced_reverse_named_batches_keep_observation_heads_and_latest_state_source ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 73 filtered out; finished in 0.05s + +``` + +Post-fix focused run: the same three authored cases, unmodified assertions, all GREEN (exit 0). + +```text + Locking 5 packages to latest compatible versions + Adding eventlog-core v0.6.0 (/crates/eventlog-core) + Adding eventlog-file v0.6.0 (/crates/eventlog-file) + Adding eventlog-postgres v0.6.0 (/crates/eventlog-postgres) + Adding eventlog-sqlite v0.6.0 (/crates/eventlog-sqlite) + Adding eventlog-tree v0.6.0 (/crates/eventlog-tree) + Compiling entity-eventlog v0.25.1 (/crates/entity-eventlog) + Finished `test` profile [unoptimized] target(s) in 3.42s + Running unittests src/lib.rs (target/debug/deps/entity_eventlog-e345c4868818aed2) + +running 3 tests +test adapter::tracked::review_tests::acknowledged_native_append_with_blank_named_key_is_refused_before_tracked_reuse ... ok +test adapter::tracked::review_tests::incremental_receipts_refuse_a_single_record_key_that_names_another_record ... ok +test adapter::tracked::review_tests::coalesced_reverse_named_batches_keep_observation_heads_and_latest_state_source ... ok + +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 71 filtered out; finished in 0.05s + +``` + +Suite command: `CARGO_BUILD_JOBS=2 CARGO_PROFILE_DEV_DEBUG=0 cargo +1.91.0 --config target/issue-51-scratch/paths.toml test -p entity-eventlog --all-features --locked` + +Suite output (exit 0; 186 passed, zero failed, one ignored performance probe): + +```text + Compiling entity-eventlog v0.25.1 (/crates/entity-eventlog) + Finished `test` profile [unoptimized] target(s) in 7.81s + Running unittests src/lib.rs (target/debug/deps/entity_eventlog-e345c4868818aed2) + +running 74 tests +test adapter::batch_read_tests::sequential_decisions_measurement ... ok +test adapter::forked_append::a_provider_fork_refusal_of_a_stream_the_append_does_not_write_is_integrity ... ok +test adapter::forked_append::a_provider_fork_refusal_of_a_written_subject_is_that_subjects_fork ... ok +test adapter::seeded_open::a_batch_whose_stored_bytes_are_not_their_own_canonical_rendering_is_refused ... ok +test adapter::seeded_open::a_batch_is_parsed_once_rather_than_once_for_every_member_as_well ... ok +test adapter::seeded_open::a_member_expectation_carrying_a_field_of_its_own_is_refused ... ok +test adapter::seeded_open::seeded_open_measurement ... ok +test adapter::seeded_open::a_batch_member_that_does_not_reproduce_its_own_record_is_refused ... ok +test adapter::seeded_open::the_batch_key_expectation_and_member_wires_admit_exactly_one_shape_each ... ok +test adapter::seeded_open::a_remembered_history_is_not_the_answer_for_one_that_differs_in_a_single_expectation ... ok +test adapter::seeded_open::a_memory_of_the_sound_store_admits_no_forgery_of_it ... ok +test adapter::seeded_open::a_remembered_digest_does_not_admit_other_bytes_under_it ... ok +test adapter::small_store_cost::small_store_open_and_batch_measurement ... ok +test adapter::tests::every_eventlog_append_error_has_an_explicit_er_classification ... ok +test adapter::tests::every_guard_refusal_has_a_stable_code_and_typed_match ... ok +test adapter::tests::the_blob_and_append_mappings_agree_on_the_only_variant_put_blob_documents ... ok +test adapter::seeded_open::an_advanced_subject_is_verified_from_its_verified_state_and_refused_where_it_breaks ... ok +test adapter::batch_read_tests::lost_append_reply_recovers_with_a_fresh_verified_capture ... ok +test adapter::batch_read_tests::stale_preflight_is_guarded_and_replayed_append_recovers_from_fresh_capture ... ok +test adapter::tracked::review_tests::acknowledged_native_append_with_blank_named_key_is_refused_before_tracked_reuse ... ok +test adapter::batch_read_tests::a_head_another_writer_moved_forces_a_whole_build_that_sees_it ... ok +test adapter::tracked::review_tests::incremental_receipts_refuse_a_single_record_key_that_names_another_record ... ok +test adapter::batch_read_tests::reads_of_an_unmoved_head_reuse_the_model_the_handle_already_verified ... ok +test adapter::tracked::tests::one_scoped_history_read_preserves_order_duplicates_and_absence ... ok +test encoding::tests::a_digest_is_bound_to_its_exact_domain ... ok +test encoding::tests::closed_binding_decode_refuses_unknown_fields_and_noncanonical_order ... ok +test encoding::tests::literal_reference_vectors_pin_framing_domains_lengths_and_escaping ... ok +test encoding::tests::source_bound_anchor_refuses_a_contradictory_envelope_source ... ok +test encoding::tests::source_bound_anchor_version_preserves_old_bytes_and_old_reader_refusal ... ok +test sync::tests::an_expired_dispatched_write_retains_its_original_key_as_uncertain ... ok +test sync::tests::an_expired_queued_call_is_cancelled_before_dispatch ... ok +test sync::tests::completion_after_a_dispatched_waiter_times_out_is_safe ... ok +test sync::tests::completion_wins_a_deadline_race_without_reclassification ... ok +test sync::tests::dispatch_deadline_cas_has_one_winner_and_cancelled_work_never_calls_the_driver ... ok +test sync::tests::drain_finishes_in_order_and_escalation_cancels_only_queued_work ... ok +test sync::tests::drop_after_shutdown_timeout_escalates_and_never_waits_for_the_worker ... ok +test sync::tests::drop_requests_closure_without_waiting_for_an_active_future ... ok +test adapter::tracked::tests::unjournaled_provider_mutation_invalidates_the_model_before_reuse ... ok +test sync::tests::outer_worker_panic_marks_finished_and_preserves_a_retirement_failure ... ok +test sync::tests::panicked_or_stopped_worker_keeps_dispatched_write_uncertainty ... ok +test adapter::seeded_open::a_blob_named_under_two_domains_is_refused_without_being_hashed_twice ... ok +test sync::tests::panics_after_dispatch_keep_read_and_write_classification ... ok +test sync::tests::production_worker_enforces_capacity_and_discards_cancelled_queue_cells ... ok +test sync::tests::production_worker_calls_are_runtime_independent_and_reentrant_safe ... ok +test adapter::seeded_open::a_store_of_named_batches_written_at_base_reads_into_the_base_model ... ok +test sync::tests::production_worker_startup_owns_and_drops_the_driver_on_its_thread ... ok +test sync::tests::provider_errors_pass_through_without_bridge_reclassification ... ok +test sync::tests::queued_shutdown_cancellation_returns_closed_for_reads_and_writes ... ok +test sync::tests::reentrant_admission_is_rejected_before_queue_accounting ... ok +test sync::tests::queue_capacity_and_closed_admission_are_deterministic ... ok +test sync::tests::retirement_timeout_later_joins_once_and_preserves_provider_failure ... ok +test sync::tests::shutdown_joins_once_and_caches_provider_retirement_failure ... ok +test sync::tests::timed_out_shutdown_reports_in_flight_identity_and_can_escalate ... ok +test sync::tests::production_worker_preserves_every_driver_error_classification ... ok +test adapter::tracked::review_tests::coalesced_reverse_named_batches_keep_observation_heads_and_latest_state_source ... ok +test sync::tests::worker_panics_cancel_real_queued_cells_without_stranding_waiters ... ok +test adapter::tracked::tests::held_reader_keeps_its_old_model_while_a_refresh_rebuilds_safely ... ok +test adapter::seeded_open::a_capture_is_hashed_once_over_rather_than_several_times_over ... ok +test adapter::tracked::tests::delta_verification_matches_full_models_and_refuses_missing_or_forged_coordinates ... ok +test adapter::batch_read_tests::one_per_entity_read_serves_each_preflight_and_is_never_reused_across_calls ... ok +test adapter::tracked::tests::tracked_unchanged_reads_and_appends_do_not_recapture_verified_prefixes ... ok +test adapter::seeded_open::every_carried_digest_is_the_digest_of_the_bytes_the_model_holds ... ok +test sync::tests::timed_out_caller_does_not_cancel_commit_and_same_key_retry_recovers_it ... ok +test adapter::batch_read_tests::a_decision_this_handle_commits_advances_its_model_by_its_own_records_only ... ok +test adapter::memory::tests::a_memory_forgets_before_what_it_actually_retains_passes_its_cap ... ok +test adapter::seeded_open::every_bound_domain_is_still_refused_when_its_blob_is_not_its_digest ... ok +test adapter::small_store_cost::adversary_every_bound_blob_is_sorted_key_json_under_either_map_backend ... ok +test sync::tests::worker_panic_closes_admission_before_terminal_drain_and_wakes_late_accepted_caller ... ok +test adapter::seeded_open::the_model_is_byte_identical_to_the_verifying_path ... ok +test sync::tests::every_executor_write_keeps_its_original_key_after_dispatch_deadline ... ok +test adapter::small_store_cost::a_cold_open_decodes_and_replays_each_record_once ... ok +test adapter::small_store_cost::a_warm_handle_decodes_and_replays_only_the_records_a_batch_adds ... ok +test adapter::small_store_cost::adversary_a_warm_handle_answers_every_rewritten_capture_as_a_cold_build ... ok +test adapter::seeded_open::a_model_advanced_by_appended_events_is_the_model_a_whole_build_produces ... ok + +test result: ok. 74 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.34s + + Running tests/adversary_guarded_write_races.rs (target/debug/deps/adversary_guarded_write_races-df43ad6cb924d100) + +running 4 tests +test adversary_a_raced_import_through_the_override_reports_what_the_unraced_import_reports ... ok +test adversary_a_fallback_refused_append_counts_its_orphaned_blobs_against_the_read_bound ... ok +test adversary_a_raced_import_reports_the_same_refusal_through_override_and_fallback ... ok +test adversary_a_batch_key_raced_by_another_command_is_refused_alike_through_override_and_fallback ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.16s + + Running tests/adversary_observation_heads.rs (target/debug/deps/adversary_observation_heads-f3d436da1036f4e7) + +running 4 tests +test a_stale_observation_recorded_after_the_other_branch_decided_does_not_break_the_merge ... ok +test a_batch_observing_then_deciding_on_a_multi_tip_subject_commits ... ok +test a_batch_writing_a_multi_tip_subject_twice_commits ... ok +test an_observation_on_a_three_tip_unforked_subject_commits_and_serves_the_decision ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s + + Running tests/adversary_r2_per_entity_reads.rs (target/debug/deps/adversary_r2_per_entity_reads-23ac8bb40ae7e5fd) + +running 4 tests +test a_read_through_a_forgotten_tenant_does_not_mint_it_a_new_identity ... ok +test r151_a_command_read_is_per_entity_and_a_handle_read_is_one_complete_capture ... ok +test a_handle_does_not_commit_commands_it_can_no_longer_read_back ... ok +test an_honest_concurrent_writer_is_a_revision_conflict_not_provider_integrity ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s + + Running tests/adversary_two_batch_capture_limit.rs (target/debug/deps/adversary_two_batch_capture_limit-4ac15ec8d2126679) + +running 1 test +test a_batch_commits_no_more_than_its_own_handle_can_read_back ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s + + Running tests/adversary_two_partial_overlap.rs (target/debug/deps/adversary_two_partial_overlap-ce95e04d7f2f16d4) + +running 2 tests +test a_partially_overlapping_group_reports_the_member_that_is_actually_occupied ... ok +test a_partially_overlapping_group_commits_no_part_of_itself ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s + + Running tests/adversary_two_port_default_document.rs (target/debug/deps/adversary_two_port_default_document-e3ad7570edf1f5c7) + +running 2 tests +test the_port_default_fails_closed_and_binds_no_blob ... ok +test the_sqlite_override_commits_a_group_with_its_blob_and_a_refused_guard_binds_neither ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/fault_acceptance.rs (target/debug/deps/fault_acceptance-47ca297aefaf1cdf) + +running 26 tests +test postgres_provider_recovers_commit_before_lost_response_when_assigned ... ok +test file_provider_recovers_commit_before_lost_response ... ok +test provision_unknown_rollback_and_unavailable_recovery_remain_uncertain ... ok +test provision_recovers_commit_before_lost_response_with_original_coordinates ... ok +test provision_recovery_is_absent_then_unavailable_while_the_append_is_in_flight ... ok +test provision_preserves_exact_tuple_conflict ... ok +test append_unknown_rollback_with_unavailable_recovery_retains_the_batch_key ... ok +test import_unknown_rollback_and_malformed_reply_keep_subject_uncertainty ... ok +test append_recovers_committed_lost_reply_and_preserves_the_original_receipt ... ok +test import_recovers_committed_lost_reply_and_restart_with_fresh_context ... ok +test unknown_commit_with_corrupt_recovery_keeps_the_original_uncertainty ... ok +test competing_provisioners_are_atomic_in_both_winner_orders ... ok +test append_retries_a_false_physical_conflict_but_refuses_a_malformed_commit_reply ... ok +test import_refuses_after_each_individual_blob_upload_boundary ... ok +test append_never_trusts_a_guard_code_without_the_typed_transaction_slot ... ok +test reads_reject_missing_tampered_and_colliding_record_indexes ... ok +test absent_global_record_race_is_atomic_in_both_winner_orders ... ok +test ordinary_and_import_record_identity_race_is_atomic_in_both_winner_orders ... ok +test append_refuses_after_each_individual_blob_upload_boundary ... ok +test returned_backend_or_unknown_commit_wins_after_a_real_guard_refusal_rollback ... ok +test rebuild_propagates_provider_failure_and_revalidates_the_fresh_capture ... ok +test a_verified_head_is_never_reused_for_a_capture_that_differs_from_it ... ok +test binding_recovery_validates_the_complete_foreign_model_before_conflict ... ok +test open_rejects_every_mutated_authoritative_capture_shape ... ok +test binding_recovery_rejects_missing_malformed_and_substituted_capture_authority ... ok +test real_projector_failures_before_and_after_apply_roll_back_append_and_import ... ok + +test result: ok. 26 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.16s + + Running tests/guarded_write_blob_binding.rs (target/debug/deps/guarded_write_blob_binding-83b7826271819f9e) + +running 10 tests +test a_binding_lost_to_a_rival_binds_no_blob_on_sqlite_and_file ... ok +test a_failed_fallback_upload_keeps_each_writes_blob_error ... ok +test a_guard_refused_import_binds_no_blob_on_sqlite_and_file ... ok +test a_guard_refused_append_binds_no_blob_on_sqlite_and_file ... ok +test every_guarded_write_binds_its_blobs_with_its_group_on_sqlite_and_file ... ok +test every_guarded_write_commits_through_the_fallback ... ok +test an_import_raced_by_a_batch_hears_the_guard_through_override_and_fallback ... ok +test a_refused_append_does_not_count_its_unbound_blobs_against_the_read_bound ... ok +test a_re_admitted_retry_of_each_guarded_write_is_answered_as_its_commit ... ok +test a_guard_refused_append_reports_the_same_refusal_through_the_fallback ... ok + +test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s + + Running tests/import_batch_blob_binding.rs (target/debug/deps/import_batch_blob_binding-7b07b35ef0cf8d92) + +running 1 test +test a_guard_refused_batch_binds_no_blob_on_the_sqlite_provider ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s + + Running tests/import_batch_record_identity.rs (target/debug/deps/import_batch_record_identity-1bcf645b68bbf572) + +running 2 tests +test a_batch_refuses_a_record_identity_two_of_its_members_share ... ok +test a_batch_naming_one_subject_twice_settles_as_two_singular_calls_settle ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s + + Running tests/import_batch_retry_admission.rs (target/debug/deps/import_batch_retry_admission-87c284df4623fafa) + +running 5 tests +test one_anchor_naming_a_record_identity_twice_is_refused ... ok +test the_guard_refuses_a_subject_another_writer_answered_for_under_different_bytes ... ok +test the_guard_refuses_a_record_identity_another_writer_committed_after_the_pre_capture ... ok +test a_batch_bearing_retry_is_admitted_over_the_commit_it_already_made ... ok +test the_guard_refuses_a_subject_its_anchor_started_and_something_has_since_appended_to ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s + + Running tests/per_entity_reads.rs (target/debug/deps/per_entity_reads-a33b2f2e4eaedd4a) + +running 7 tests +test a_command_refuses_a_substituted_stream_identity_on_the_sqlite_provider ... ok +test a_command_refuses_a_substituted_stream_identity_on_the_tree_provider ... ok +test a_command_refuses_a_forged_index_row_of_its_own_subject_on_the_sqlite_provider ... ok +test a_command_on_one_subject_reads_only_its_own_stream_on_the_sqlite_provider ... ok +test a_command_refuses_a_forged_index_row_of_its_own_subject_on_the_tree_provider ... ok +test a_command_on_one_subject_reads_only_its_own_stream_on_the_tree_provider ... ok +test a_command_on_one_subject_reads_only_its_own_stream_on_the_file_provider ... ok + +test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.16s + + Running tests/provider_facades.rs (target/debug/deps/provider_facades-2cdb2f9041ebd9a9) + +running 11 tests +test opening_an_absent_file_authority_does_not_create_provider_bytes ... ok +test opening_an_absent_sqlite_authority_does_not_create_provider_bytes ... ok +test legacy_import_revalidates_the_complete_source_before_any_destination_write ... ok +test opening_native_stores_without_er_bindings_preserves_provider_authority ... ok +test legacy_import_refuses_a_different_source_identity_for_an_identical_bare_boundary ... ok +test sqlite_memory_facade_uses_the_same_complete_surface ... ok +test legacy_import_retains_settled_progress_and_refuses_a_changed_source_boundary ... ok +test sqlite_file_provision_returns_the_exact_reopen_authority ... ok +test legacy_file_import_is_exact_resumable_and_provider_verified ... ok +test file_facade_preserves_atomic_groups_queries_retries_and_restart ... ok +test file_facade_groups_are_process_atomic_and_survive_a_publication_crash ... ok + +test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 19.73s + + Running tests/providers.rs (target/debug/deps/providers-2ef332b306f08368) + +running 15 tests +test postgres_bridge_awaits_worker_owned_provider_retirement_when_assigned ... ok +test postgres_provider_provisions_replays_opens_and_rebuilds_when_assigned ... ok +test an_empty_batch_reaches_no_provider ... ok +test a_batch_naming_one_subject_twice_with_different_anchors_is_refused_before_any_write ... ok +test synchronous_bridge_operates_without_a_caller_runtime ... ok +test synchronous_bridge_owns_reopened_provider_and_reports_retirement ... ok +test synchronous_bridge_operates_inside_a_multithread_runtime ... ok +test a_refused_member_leaves_no_part_of_the_batch_committed ... ok +test synchronous_bridge_startup_refuses_missing_drifted_and_dirty_projection_admission ... ok +test a_batch_import_commits_through_the_port_default_on_the_sqlite_provider ... ok +test a_batch_import_writes_the_same_bytes_as_singular_imports_from_one_capture ... ok +test sqlite_memory_provider_provisions_replays_opens_and_rebuilds ... ok +test sqlite_file_provider_provisions_replays_opens_and_rebuilds ... ok +test tree_provider_provisions_replays_opens_and_rebuilds ... ok +test file_provider_provisions_replays_opens_and_rebuilds ... ok + +test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s + + Running tests/reviewer_binding_unknown_conflict.rs (target/debug/deps/reviewer_binding_unknown_conflict-06c98a5180325eb3) + +running 2 tests +test a_foreign_binding_is_not_a_conflict_until_its_derived_row_is_validated ... ok +test unknown_commit_recovery_preserves_a_conclusive_foreign_binding_conflict ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running tests/service_3_composition.rs (target/debug/deps/service_3_composition-3bd3469bd382ceea) + +running 5 tests +test postgres_provider_preserves_complete_service_3_composition_when_assigned ... ok +test sqlite_bridge_crosses_service_3_retry_history_and_reopen ... ok +test sqlite_memory_provider_preserves_complete_service_3_composition ... ok +test sqlite_file_provider_preserves_complete_service_3_composition ... ok +test file_provider_preserves_complete_service_3_composition ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.40s + + Running tests/shared_clock_cost.rs (target/debug/deps/shared_clock_cost-2fb1c8a8f0dbdd18) + +running 1 test +test warm_shared_clock_batches_remain_within_twice_small_store_cost ... ignored, release performance probe; run explicitly on a quiet host + +test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/tree_branches.rs (target/debug/deps/tree_branches-676fbb82d076e08a) + +running 10 tests +test two_branches_that_recorded_different_subjects_merge_into_one_store ... ok +test an_observation_on_one_branch_and_a_decision_on_the_other_do_not_fork_the_subject ... ok +test two_branches_that_only_observed_one_revision_do_not_fork_the_subject ... ok +test a_decision_after_an_observation_merged_beside_a_decision_extends_the_subject ... ok +test a_subject_both_branches_changed_is_forked_and_the_rest_of_the_store_still_serves ... ok +test a_decision_on_each_branch_still_forks_and_its_heads_are_the_decisions ... ok +test a_merge_decision_joins_a_forked_subject_and_it_serves_again_after_reopening ... ok +test a_fork_whose_observation_replays_after_the_other_branch_still_opens_as_forked ... ok +test a_refused_command_is_recorded_once_and_changes_no_subject ... ok +test an_observation_after_a_merge_decision_attaches_to_the_merge ... ok + +test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.44s + + Running tests/tree_text_blobs.rs (target/debug/deps/tree_text_blobs-5ee29b9322322ddd) + +running 1 test +test a_first_layout_store_reads_the_same_after_its_texts_are_kept_once_and_keeps_recording ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s + + Doc-tests entity_eventlog + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + +``` + +`cargo +1.91.0 fmt --all -- --check`: exit 0, no output. Provider source SHA-256 manifest matched before and after compilation/testing. The local path dependency lock changes were restored byte-for-byte from the review-start lock copy. The source manifest and original logs are retained beside this report. + +| File:line | Verdict | Origin | Finding and resolution | +|---|---|---|---| +| crates/entity-eventlog/src/adapter/tracked.rs:310 | CONFIRMED | introduced | The new suffix replay omitted receipt batch-key validity and SingleRecord identity/index constraints enforced by full history verification. Real native SQLite append can therefore make tracked reads expose data that full verification rejects. Implementor added the bounded checks immediately before suffix replay; both original red tests and the complete package now pass. | + +Measured: original isolated blank-key test exit 101 at `review_tests.rs:415`, returning `Ok(Some(Committed(... Named(""))))`; full verification in the same test already returned the required invalid-key `CorruptHistory`. + +Reachability: default ER projector plus public SQLite guarded-with-blobs native append, after a warm ProviderTracked cache. This is not a claim that the validated ER executor creates malformed keys. The generic SingleRecord probe is a fabricated-delta defense test, not a separately confirmed native-provider attack. + +Origin: the warm incremental path is new in this unit; the retained full verifier refuses the exact same captured native append. The base was read without moving the shared tree. No pre-existing defect is inferred from permissive native append alone. + +Attacked without another break: coalesced whole-batch membership, lexical-versus-event ordering, observation heads and last state-producing record, bidirectional exact projection content, and canonical bytes/full-model equivalence for those records. + +Existing tests also executed: omitted/forged delta coordinates, retained reader fallback, no-head-change provider mutation, scoped order/duplicates/absence, and bridge lifecycle/cancellation. Their inherited status is distinguished from newly authored probes. + +Read-only review also covered checkpoint generation retry, failed-advance full fallback, imported/lineage fallback, existing constructor defaults and scoped request cancellation arms. No new deterministic concurrency schedule or imported-lineage adversarial fixture was added in this bounded pass; those paths are not claimed exhaustively tested here. No production mutation control was needed for the receipt tests: both failed against the originally green implementation. + +Outside-worktree writes: none. Managed-tree lease metadata was handled through the required CLI lifecycle hooks; no repository, report, test, log or scratch artifact was written outside the assigned tree. + +```findings +- file: crates/entity-eventlog/src/adapter/tracked.rs + line: 310 + category: contract-drift + severity: blocker + verdict: CONFIRMED + origin: introduced + message: Original incremental suffix replay accepted invalid receipt batch keys refused by full verification; real SQLite native append reproduced it. Implementor added bounded key and SingleRecord coordinate checks; unchanged red-capable tests and all 186 package tests now pass. +``` diff --git a/.engineering/planning/review-result/issue-51-runtime-recheck.md b/.engineering/planning/review-result/issue-51-runtime-recheck.md new file mode 100644 index 00000000..81635e75 --- /dev/null +++ b/.engineering/planning/review-result/issue-51-runtime-recheck.md @@ -0,0 +1,358 @@ +--- +format: aep.planning-md/3 +id: review-result:issue-51-runtime-recheck +kind: review-result +status: active +title: Incremental runtime receipt correction recheck +relations: +- reviews: story:bounded-batch-and-facade-reads +revision: 1 +--- +unit: #51 runtime working tree based on f57bf9753abec7aaa5ff9103bd9b66e29f3398cd, after implementor receipt guard +verdict: nothing found +cases: executed 186→186, red 0 +origin: introduced 0 / pre-existing 0 / undecided 0 +wrote-outside-worktree: none +needs-coordinator: none + +```text +git --no-pager diff --stat + crates/entity-eventlog/src/adapter.rs | 52 +++++++++++- + crates/entity-eventlog/src/adapter/scoped.rs | 15 +++- + crates/entity-eventlog/src/facade.rs | 121 ++++++++++++++++++++++++++- + crates/entity-eventlog/src/lib.rs | 6 +- + crates/entity-eventlog/src/sync.rs | 121 +++++++++++++++++++++++++-- + 5 files changed, 298 insertions(+), 17 deletions(-) +``` + +The diff includes inherited implementation changes. Reviewer writes remain only the new 419-line `adapter/tracked/review_tests.rs` and the coordinator-approved test-only module declaration in `tracked.rs`; the implementor owns the production fix. Original defect and isolated red outputs are preserved in `report.md`. No test assertion was weakened for this recheck. + +Both receipt regressions now pass: real SQLite/default-projector native blank-key append is refused with the same invalid-key `CorruptHistory` as full verification, and fabricated SingleRecord mismatch is refused by incremental admission. The coalesced-batch ordering, observation head, latest state source and missing-member test also remains green. + +The focused command ran `adapter::tracked::review_tests` and executed exactly three cases. Full package command: `CARGO_BUILD_JOBS=2 CARGO_PROFILE_DEV_DEBUG=0 cargo +1.91.0 --config target/issue-51-scratch/paths.toml test -p entity-eventlog --all-features --locked`. Exact output follows. Machine-specific checkout prefixes alone are redacted to `` / ``. + +```text + Compiling entity-eventlog v0.25.1 (/crates/entity-eventlog) + Finished `test` profile [unoptimized] target(s) in 7.81s + Running unittests src/lib.rs (target/debug/deps/entity_eventlog-e345c4868818aed2) + +running 74 tests +test adapter::batch_read_tests::sequential_decisions_measurement ... ok +test adapter::forked_append::a_provider_fork_refusal_of_a_stream_the_append_does_not_write_is_integrity ... ok +test adapter::forked_append::a_provider_fork_refusal_of_a_written_subject_is_that_subjects_fork ... ok +test adapter::seeded_open::a_batch_whose_stored_bytes_are_not_their_own_canonical_rendering_is_refused ... ok +test adapter::seeded_open::a_batch_is_parsed_once_rather_than_once_for_every_member_as_well ... ok +test adapter::seeded_open::a_member_expectation_carrying_a_field_of_its_own_is_refused ... ok +test adapter::seeded_open::seeded_open_measurement ... ok +test adapter::seeded_open::a_batch_member_that_does_not_reproduce_its_own_record_is_refused ... ok +test adapter::seeded_open::the_batch_key_expectation_and_member_wires_admit_exactly_one_shape_each ... ok +test adapter::seeded_open::a_remembered_history_is_not_the_answer_for_one_that_differs_in_a_single_expectation ... ok +test adapter::seeded_open::a_memory_of_the_sound_store_admits_no_forgery_of_it ... ok +test adapter::seeded_open::a_remembered_digest_does_not_admit_other_bytes_under_it ... ok +test adapter::small_store_cost::small_store_open_and_batch_measurement ... ok +test adapter::tests::every_eventlog_append_error_has_an_explicit_er_classification ... ok +test adapter::tests::every_guard_refusal_has_a_stable_code_and_typed_match ... ok +test adapter::tests::the_blob_and_append_mappings_agree_on_the_only_variant_put_blob_documents ... ok +test adapter::seeded_open::an_advanced_subject_is_verified_from_its_verified_state_and_refused_where_it_breaks ... ok +test adapter::batch_read_tests::lost_append_reply_recovers_with_a_fresh_verified_capture ... ok +test adapter::batch_read_tests::stale_preflight_is_guarded_and_replayed_append_recovers_from_fresh_capture ... ok +test adapter::tracked::review_tests::acknowledged_native_append_with_blank_named_key_is_refused_before_tracked_reuse ... ok +test adapter::batch_read_tests::a_head_another_writer_moved_forces_a_whole_build_that_sees_it ... ok +test adapter::tracked::review_tests::incremental_receipts_refuse_a_single_record_key_that_names_another_record ... ok +test adapter::batch_read_tests::reads_of_an_unmoved_head_reuse_the_model_the_handle_already_verified ... ok +test adapter::tracked::tests::one_scoped_history_read_preserves_order_duplicates_and_absence ... ok +test encoding::tests::a_digest_is_bound_to_its_exact_domain ... ok +test encoding::tests::closed_binding_decode_refuses_unknown_fields_and_noncanonical_order ... ok +test encoding::tests::literal_reference_vectors_pin_framing_domains_lengths_and_escaping ... ok +test encoding::tests::source_bound_anchor_refuses_a_contradictory_envelope_source ... ok +test encoding::tests::source_bound_anchor_version_preserves_old_bytes_and_old_reader_refusal ... ok +test sync::tests::an_expired_dispatched_write_retains_its_original_key_as_uncertain ... ok +test sync::tests::an_expired_queued_call_is_cancelled_before_dispatch ... ok +test sync::tests::completion_after_a_dispatched_waiter_times_out_is_safe ... ok +test sync::tests::completion_wins_a_deadline_race_without_reclassification ... ok +test sync::tests::dispatch_deadline_cas_has_one_winner_and_cancelled_work_never_calls_the_driver ... ok +test sync::tests::drain_finishes_in_order_and_escalation_cancels_only_queued_work ... ok +test sync::tests::drop_after_shutdown_timeout_escalates_and_never_waits_for_the_worker ... ok +test sync::tests::drop_requests_closure_without_waiting_for_an_active_future ... ok +test adapter::tracked::tests::unjournaled_provider_mutation_invalidates_the_model_before_reuse ... ok +test sync::tests::outer_worker_panic_marks_finished_and_preserves_a_retirement_failure ... ok +test sync::tests::panicked_or_stopped_worker_keeps_dispatched_write_uncertainty ... ok +test adapter::seeded_open::a_blob_named_under_two_domains_is_refused_without_being_hashed_twice ... ok +test sync::tests::panics_after_dispatch_keep_read_and_write_classification ... ok +test sync::tests::production_worker_enforces_capacity_and_discards_cancelled_queue_cells ... ok +test sync::tests::production_worker_calls_are_runtime_independent_and_reentrant_safe ... ok +test adapter::seeded_open::a_store_of_named_batches_written_at_base_reads_into_the_base_model ... ok +test sync::tests::production_worker_startup_owns_and_drops_the_driver_on_its_thread ... ok +test sync::tests::provider_errors_pass_through_without_bridge_reclassification ... ok +test sync::tests::queued_shutdown_cancellation_returns_closed_for_reads_and_writes ... ok +test sync::tests::reentrant_admission_is_rejected_before_queue_accounting ... ok +test sync::tests::queue_capacity_and_closed_admission_are_deterministic ... ok +test sync::tests::retirement_timeout_later_joins_once_and_preserves_provider_failure ... ok +test sync::tests::shutdown_joins_once_and_caches_provider_retirement_failure ... ok +test sync::tests::timed_out_shutdown_reports_in_flight_identity_and_can_escalate ... ok +test sync::tests::production_worker_preserves_every_driver_error_classification ... ok +test adapter::tracked::review_tests::coalesced_reverse_named_batches_keep_observation_heads_and_latest_state_source ... ok +test sync::tests::worker_panics_cancel_real_queued_cells_without_stranding_waiters ... ok +test adapter::tracked::tests::held_reader_keeps_its_old_model_while_a_refresh_rebuilds_safely ... ok +test adapter::seeded_open::a_capture_is_hashed_once_over_rather_than_several_times_over ... ok +test adapter::tracked::tests::delta_verification_matches_full_models_and_refuses_missing_or_forged_coordinates ... ok +test adapter::batch_read_tests::one_per_entity_read_serves_each_preflight_and_is_never_reused_across_calls ... ok +test adapter::tracked::tests::tracked_unchanged_reads_and_appends_do_not_recapture_verified_prefixes ... ok +test adapter::seeded_open::every_carried_digest_is_the_digest_of_the_bytes_the_model_holds ... ok +test sync::tests::timed_out_caller_does_not_cancel_commit_and_same_key_retry_recovers_it ... ok +test adapter::batch_read_tests::a_decision_this_handle_commits_advances_its_model_by_its_own_records_only ... ok +test adapter::memory::tests::a_memory_forgets_before_what_it_actually_retains_passes_its_cap ... ok +test adapter::seeded_open::every_bound_domain_is_still_refused_when_its_blob_is_not_its_digest ... ok +test adapter::small_store_cost::adversary_every_bound_blob_is_sorted_key_json_under_either_map_backend ... ok +test sync::tests::worker_panic_closes_admission_before_terminal_drain_and_wakes_late_accepted_caller ... ok +test adapter::seeded_open::the_model_is_byte_identical_to_the_verifying_path ... ok +test sync::tests::every_executor_write_keeps_its_original_key_after_dispatch_deadline ... ok +test adapter::small_store_cost::a_cold_open_decodes_and_replays_each_record_once ... ok +test adapter::small_store_cost::a_warm_handle_decodes_and_replays_only_the_records_a_batch_adds ... ok +test adapter::small_store_cost::adversary_a_warm_handle_answers_every_rewritten_capture_as_a_cold_build ... ok +test adapter::seeded_open::a_model_advanced_by_appended_events_is_the_model_a_whole_build_produces ... ok + +test result: ok. 74 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.34s + + Running tests/adversary_guarded_write_races.rs (target/debug/deps/adversary_guarded_write_races-df43ad6cb924d100) + +running 4 tests +test adversary_a_raced_import_through_the_override_reports_what_the_unraced_import_reports ... ok +test adversary_a_fallback_refused_append_counts_its_orphaned_blobs_against_the_read_bound ... ok +test adversary_a_raced_import_reports_the_same_refusal_through_override_and_fallback ... ok +test adversary_a_batch_key_raced_by_another_command_is_refused_alike_through_override_and_fallback ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.16s + + Running tests/adversary_observation_heads.rs (target/debug/deps/adversary_observation_heads-f3d436da1036f4e7) + +running 4 tests +test a_stale_observation_recorded_after_the_other_branch_decided_does_not_break_the_merge ... ok +test a_batch_observing_then_deciding_on_a_multi_tip_subject_commits ... ok +test a_batch_writing_a_multi_tip_subject_twice_commits ... ok +test an_observation_on_a_three_tip_unforked_subject_commits_and_serves_the_decision ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s + + Running tests/adversary_r2_per_entity_reads.rs (target/debug/deps/adversary_r2_per_entity_reads-23ac8bb40ae7e5fd) + +running 4 tests +test a_read_through_a_forgotten_tenant_does_not_mint_it_a_new_identity ... ok +test r151_a_command_read_is_per_entity_and_a_handle_read_is_one_complete_capture ... ok +test a_handle_does_not_commit_commands_it_can_no_longer_read_back ... ok +test an_honest_concurrent_writer_is_a_revision_conflict_not_provider_integrity ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s + + Running tests/adversary_two_batch_capture_limit.rs (target/debug/deps/adversary_two_batch_capture_limit-4ac15ec8d2126679) + +running 1 test +test a_batch_commits_no_more_than_its_own_handle_can_read_back ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s + + Running tests/adversary_two_partial_overlap.rs (target/debug/deps/adversary_two_partial_overlap-ce95e04d7f2f16d4) + +running 2 tests +test a_partially_overlapping_group_reports_the_member_that_is_actually_occupied ... ok +test a_partially_overlapping_group_commits_no_part_of_itself ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s + + Running tests/adversary_two_port_default_document.rs (target/debug/deps/adversary_two_port_default_document-e3ad7570edf1f5c7) + +running 2 tests +test the_port_default_fails_closed_and_binds_no_blob ... ok +test the_sqlite_override_commits_a_group_with_its_blob_and_a_refused_guard_binds_neither ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/fault_acceptance.rs (target/debug/deps/fault_acceptance-47ca297aefaf1cdf) + +running 26 tests +test postgres_provider_recovers_commit_before_lost_response_when_assigned ... ok +test file_provider_recovers_commit_before_lost_response ... ok +test provision_unknown_rollback_and_unavailable_recovery_remain_uncertain ... ok +test provision_recovers_commit_before_lost_response_with_original_coordinates ... ok +test provision_recovery_is_absent_then_unavailable_while_the_append_is_in_flight ... ok +test provision_preserves_exact_tuple_conflict ... ok +test append_unknown_rollback_with_unavailable_recovery_retains_the_batch_key ... ok +test import_unknown_rollback_and_malformed_reply_keep_subject_uncertainty ... ok +test append_recovers_committed_lost_reply_and_preserves_the_original_receipt ... ok +test import_recovers_committed_lost_reply_and_restart_with_fresh_context ... ok +test unknown_commit_with_corrupt_recovery_keeps_the_original_uncertainty ... ok +test competing_provisioners_are_atomic_in_both_winner_orders ... ok +test append_retries_a_false_physical_conflict_but_refuses_a_malformed_commit_reply ... ok +test import_refuses_after_each_individual_blob_upload_boundary ... ok +test append_never_trusts_a_guard_code_without_the_typed_transaction_slot ... ok +test reads_reject_missing_tampered_and_colliding_record_indexes ... ok +test absent_global_record_race_is_atomic_in_both_winner_orders ... ok +test ordinary_and_import_record_identity_race_is_atomic_in_both_winner_orders ... ok +test append_refuses_after_each_individual_blob_upload_boundary ... ok +test returned_backend_or_unknown_commit_wins_after_a_real_guard_refusal_rollback ... ok +test rebuild_propagates_provider_failure_and_revalidates_the_fresh_capture ... ok +test a_verified_head_is_never_reused_for_a_capture_that_differs_from_it ... ok +test binding_recovery_validates_the_complete_foreign_model_before_conflict ... ok +test open_rejects_every_mutated_authoritative_capture_shape ... ok +test binding_recovery_rejects_missing_malformed_and_substituted_capture_authority ... ok +test real_projector_failures_before_and_after_apply_roll_back_append_and_import ... ok + +test result: ok. 26 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.16s + + Running tests/guarded_write_blob_binding.rs (target/debug/deps/guarded_write_blob_binding-83b7826271819f9e) + +running 10 tests +test a_binding_lost_to_a_rival_binds_no_blob_on_sqlite_and_file ... ok +test a_failed_fallback_upload_keeps_each_writes_blob_error ... ok +test a_guard_refused_import_binds_no_blob_on_sqlite_and_file ... ok +test a_guard_refused_append_binds_no_blob_on_sqlite_and_file ... ok +test every_guarded_write_binds_its_blobs_with_its_group_on_sqlite_and_file ... ok +test every_guarded_write_commits_through_the_fallback ... ok +test an_import_raced_by_a_batch_hears_the_guard_through_override_and_fallback ... ok +test a_refused_append_does_not_count_its_unbound_blobs_against_the_read_bound ... ok +test a_re_admitted_retry_of_each_guarded_write_is_answered_as_its_commit ... ok +test a_guard_refused_append_reports_the_same_refusal_through_the_fallback ... ok + +test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s + + Running tests/import_batch_blob_binding.rs (target/debug/deps/import_batch_blob_binding-7b07b35ef0cf8d92) + +running 1 test +test a_guard_refused_batch_binds_no_blob_on_the_sqlite_provider ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s + + Running tests/import_batch_record_identity.rs (target/debug/deps/import_batch_record_identity-1bcf645b68bbf572) + +running 2 tests +test a_batch_refuses_a_record_identity_two_of_its_members_share ... ok +test a_batch_naming_one_subject_twice_settles_as_two_singular_calls_settle ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s + + Running tests/import_batch_retry_admission.rs (target/debug/deps/import_batch_retry_admission-87c284df4623fafa) + +running 5 tests +test one_anchor_naming_a_record_identity_twice_is_refused ... ok +test the_guard_refuses_a_subject_another_writer_answered_for_under_different_bytes ... ok +test the_guard_refuses_a_record_identity_another_writer_committed_after_the_pre_capture ... ok +test a_batch_bearing_retry_is_admitted_over_the_commit_it_already_made ... ok +test the_guard_refuses_a_subject_its_anchor_started_and_something_has_since_appended_to ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s + + Running tests/per_entity_reads.rs (target/debug/deps/per_entity_reads-a33b2f2e4eaedd4a) + +running 7 tests +test a_command_refuses_a_substituted_stream_identity_on_the_sqlite_provider ... ok +test a_command_refuses_a_substituted_stream_identity_on_the_tree_provider ... ok +test a_command_refuses_a_forged_index_row_of_its_own_subject_on_the_sqlite_provider ... ok +test a_command_on_one_subject_reads_only_its_own_stream_on_the_sqlite_provider ... ok +test a_command_refuses_a_forged_index_row_of_its_own_subject_on_the_tree_provider ... ok +test a_command_on_one_subject_reads_only_its_own_stream_on_the_tree_provider ... ok +test a_command_on_one_subject_reads_only_its_own_stream_on_the_file_provider ... ok + +test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.16s + + Running tests/provider_facades.rs (target/debug/deps/provider_facades-2cdb2f9041ebd9a9) + +running 11 tests +test opening_an_absent_file_authority_does_not_create_provider_bytes ... ok +test opening_an_absent_sqlite_authority_does_not_create_provider_bytes ... ok +test legacy_import_revalidates_the_complete_source_before_any_destination_write ... ok +test opening_native_stores_without_er_bindings_preserves_provider_authority ... ok +test legacy_import_refuses_a_different_source_identity_for_an_identical_bare_boundary ... ok +test sqlite_memory_facade_uses_the_same_complete_surface ... ok +test legacy_import_retains_settled_progress_and_refuses_a_changed_source_boundary ... ok +test sqlite_file_provision_returns_the_exact_reopen_authority ... ok +test legacy_file_import_is_exact_resumable_and_provider_verified ... ok +test file_facade_preserves_atomic_groups_queries_retries_and_restart ... ok +test file_facade_groups_are_process_atomic_and_survive_a_publication_crash ... ok + +test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 19.73s + + Running tests/providers.rs (target/debug/deps/providers-2ef332b306f08368) + +running 15 tests +test postgres_bridge_awaits_worker_owned_provider_retirement_when_assigned ... ok +test postgres_provider_provisions_replays_opens_and_rebuilds_when_assigned ... ok +test an_empty_batch_reaches_no_provider ... ok +test a_batch_naming_one_subject_twice_with_different_anchors_is_refused_before_any_write ... ok +test synchronous_bridge_operates_without_a_caller_runtime ... ok +test synchronous_bridge_owns_reopened_provider_and_reports_retirement ... ok +test synchronous_bridge_operates_inside_a_multithread_runtime ... ok +test a_refused_member_leaves_no_part_of_the_batch_committed ... ok +test synchronous_bridge_startup_refuses_missing_drifted_and_dirty_projection_admission ... ok +test a_batch_import_commits_through_the_port_default_on_the_sqlite_provider ... ok +test a_batch_import_writes_the_same_bytes_as_singular_imports_from_one_capture ... ok +test sqlite_memory_provider_provisions_replays_opens_and_rebuilds ... ok +test sqlite_file_provider_provisions_replays_opens_and_rebuilds ... ok +test tree_provider_provisions_replays_opens_and_rebuilds ... ok +test file_provider_provisions_replays_opens_and_rebuilds ... ok + +test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s + + Running tests/reviewer_binding_unknown_conflict.rs (target/debug/deps/reviewer_binding_unknown_conflict-06c98a5180325eb3) + +running 2 tests +test a_foreign_binding_is_not_a_conflict_until_its_derived_row_is_validated ... ok +test unknown_commit_recovery_preserves_a_conclusive_foreign_binding_conflict ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s + + Running tests/service_3_composition.rs (target/debug/deps/service_3_composition-3bd3469bd382ceea) + +running 5 tests +test postgres_provider_preserves_complete_service_3_composition_when_assigned ... ok +test sqlite_bridge_crosses_service_3_retry_history_and_reopen ... ok +test sqlite_memory_provider_preserves_complete_service_3_composition ... ok +test sqlite_file_provider_preserves_complete_service_3_composition ... ok +test file_provider_preserves_complete_service_3_composition ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.40s + + Running tests/shared_clock_cost.rs (target/debug/deps/shared_clock_cost-2fb1c8a8f0dbdd18) + +running 1 test +test warm_shared_clock_batches_remain_within_twice_small_store_cost ... ignored, release performance probe; run explicitly on a quiet host + +test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/tree_branches.rs (target/debug/deps/tree_branches-676fbb82d076e08a) + +running 10 tests +test two_branches_that_recorded_different_subjects_merge_into_one_store ... ok +test an_observation_on_one_branch_and_a_decision_on_the_other_do_not_fork_the_subject ... ok +test two_branches_that_only_observed_one_revision_do_not_fork_the_subject ... ok +test a_decision_after_an_observation_merged_beside_a_decision_extends_the_subject ... ok +test a_subject_both_branches_changed_is_forked_and_the_rest_of_the_store_still_serves ... ok +test a_decision_on_each_branch_still_forks_and_its_heads_are_the_decisions ... ok +test a_merge_decision_joins_a_forked_subject_and_it_serves_again_after_reopening ... ok +test a_fork_whose_observation_replays_after_the_other_branch_still_opens_as_forked ... ok +test a_refused_command_is_recorded_once_and_changes_no_subject ... ok +test an_observation_after_a_merge_decision_attaches_to_the_merge ... ok + +test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.44s + + Running tests/tree_text_blobs.rs (target/debug/deps/tree_text_blobs-5ee29b9322322ddd) + +running 1 test +test a_first_layout_store_reads_the_same_after_its_texts_are_kept_once_and_keeps_recording ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s + + Doc-tests entity_eventlog + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + +``` + +Exit 0: 186 passed, zero failed, one ignored performance probe. Formatting check exited 0 with no output. Provider source fingerprints matched before and after the run, and Cargo.lock was restored. `reviewed-source.sha256` identifies runtime/test bytes covered by this recheck. No additional finding remained. Read-only concurrency/import/lineage inspection limits remain as stated in the initial report; this is not an exhaustive proof of all schedules or malformed providers. + +Outside-worktree artifacts: none. + +```findings +[] +``` diff --git a/.engineering/planning/story/bounded-batch-and-facade-reads.md b/.engineering/planning/story/bounded-batch-and-facade-reads.md new file mode 100644 index 00000000..c9835200 --- /dev/null +++ b/.engineering/planning/story/bounded-batch-and-facade-reads.md @@ -0,0 +1,167 @@ +--- +format: aep.planning-md/3 +id: story:bounded-batch-and-facade-reads +kind: story +status: implemented +title: Bound batch execution and facade reads to verified relevant records +refs: +- provider: github + reference: beyond10x/entity-runtime#51 +relations: +- serves: vision:O2 +- informed_by: story:commands-read-their-entities-and-observations-do-not-fork +scope: +- confidence: cited + path: .github/workflows/gate.yml +- confidence: cited + path: AGENTS.md +- confidence: cited + path: CHANGELOG.md +- confidence: cited + path: Cargo.lock +- confidence: cited + path: Taskfile.yml +- confidence: cited + path: checks/ess-conformance +- confidence: cited + path: crates/entity-cli/Cargo.toml +- confidence: cited + path: crates/entity-eventlog +- confidence: cited + path: crates/entity-eventlog/Cargo.toml +- confidence: cited + path: crates/entity-eventlog/src/adapter.rs +- confidence: cited + path: crates/entity-eventlog/src/adapter/scoped.rs +- confidence: cited + path: crates/entity-eventlog/src/adapter/tracked.rs +- confidence: cited + path: crates/entity-eventlog/src/adapter/tracked/review_tests.rs +- confidence: cited + path: crates/entity-eventlog/src/facade.rs +- confidence: cited + path: crates/entity-eventlog/src/lib.rs +- confidence: cited + path: crates/entity-eventlog/src/sync.rs +- confidence: cited + path: crates/entity-eventlog/tests/shared_clock_cost.rs +- confidence: cited + path: crates/entity-postgres/Cargo.toml +- confidence: cited + path: crates/entity-sqlite/Cargo.toml +- confidence: cited + path: docs/design/eventlog-recorded-adapter-v0.1.md +- confidence: cited + path: docs/design/eventlog-recorded-sync-bridge-v0.1.md +- confidence: cited + path: docs/ess/README.md +- confidence: cited + path: docs/ess/evidence/provider-tracking +- confidence: cited + path: docs/requirements.md +- confidence: cited + path: ess/provider-tracking +revision: 20 +transitions: +- {from: "draft", to: "proposed", at: "2026-10-03T15:35:45Z", actor: "human:timo", revision: 10} +- {from: "proposed", to: "active", at: "2026-10-03T15:35:45Z", actor: "human:timo", revision: 11} +- {from: "active", to: "implemented", at: "2026-10-03T16:39:23Z", actor: "human:timo", revision: 19, decided_on: {"recorded":{"test_result":1,"metric_observation":2,"review_outcome":4,"verification":1,"ess_conformance_coverage_v1":2}}} +--- +## Context + +GitHub issue #51 provides consumer timings and the shared-clock batch-closure reproducer: https://github.com/beyond10x/entity-runtime/issues/51. It requests bounded batch verification, subject-scoped facade history and batch reads including multi-subject histories, and a cheap verified unchanged answer without losing altered-blob detection. Optional startup snapshot reuse is also in scope if the provider can prove it safe. + +## Acceptance + +Named scenarios shared_clock_batch_cost_is_bounded, facade_reads_are_subject_scoped, multi_subject_histories_use_one_scope, and unchanged_capture_preserves_tamper_detection demonstrate bounded relevant-record reads with the reported scale probe within 2x, preserve atomic whole-batch verification and tamper refusals, and provide verified snapshot reuse only when supported by provider authority. + +## Delivery + +One issue-fix integration branch and one PR with issues #49 and #50. Measure baseline and treatment, preserve integrity, and record any provider capability limitation rather than weakening verification. No consumer repins or release are requested. + +## Initial scope assessment (superseded below) + +Derived 2026-10-03 by aep:story-scoper. + +- Primary surface: crates/entity-eventlog — cited. +- Files: src/adapter/scoped.rs:181, src/adapter.rs:1384, src/sync.rs:632, src/facade.rs:214 within that crate — cited. +- Tests: tests/per_entity_reads.rs, tests/adversary_r2_per_entity_reads.rs, tests/provider_facades.rs, src/adapter/small_store_cost.rs within that crate — cited. +- Documents: docs/design/eventlog-recorded-adapter-v0.1.md, docs/design/eventlog-recorded-sync-bridge-v0.1.md, docs/requirements.md, CHANGELOG.md — cited. +- Additional ESS provider component and conformance adapter — inferred; docs/ess/README.md explicitly excludes Eventlog provider behavior from the composed five-library specification. +- Confidence: high; the mechanism remains present and R-151 specifies whole-batch verification — cited. +- Collision: other Eventlog adapter work and shared requirements/changelog edits — cited. +- Safety: scoped reads retry inconsistent observations three times then fall back to complete capture (src/adapter/scoped.rs:148) — cited, source-walk level 2, unproven by execution. + +## Feasible local work and unresolved dependency + +Explicit scoped facade APIs and one-operation read_histories can use existing ReadScope and retain whole-batch verification. Existing handle reads promise complete tenant validation; changing them silently would narrow integrity coverage. New explicit scoped methods can preserve that existing contract. + +This alone does not meet the issue: recursive batch closure remains, the clock's own history grows, and the pinned Eventlog API has no verified unchanged token. At Eventlog rev 0a0484634e8c640be29d6b6541d6cc1c1aaef7d3, crates/eventlog-core/src/capture.rs:273-336 provides complete/deferred captures; deferred capture may succeed where a content-read refusal occurs. src/lib.rs:1005-1017 projection_get supplies no authenticated checkpoint, and :1149-1179 read_many supplies no shared snapshot or verified token. Existing adapter/memory.rs:143-175,248-258 reuses verification only after comparing actual bytes. + +A checkpoint or changed-content authority needs a separately accepted provider contract; a head fingerprint cannot detect altered blob bytes. Removing co-member recursion without a replacement proof would weaken verification or force complete fallback. Therefore the complete #51 acceptance is blocked on that contract. Provider ESS coverage is also needed before the proposed scenario names can be called executed ESS scenarios. + +## Provider investigation after approval + +Current Eventlog main 06c1e99c86c7130169c7ed584ec7326f7e9a546e has the same crates tree as pinned 0a0484634e8c640be29d6b6541d6cc1c1aaef7d3; a dependency update alone supplies no runtime capability. + +A feasible SQLite warm path is inferred, not yet implemented: provider-owned connection-local data_version plus own-write and schema generations, sampled under its mutex and transaction, can attest to unchanged SQL-visible state. A bounded journal of acknowledged guarded append groups could then supply proven deltas. Other writes, external SQL mutation, uncertainty, journal expiry and mismatched limits or provider identity must reset to complete verification. A mere event-head fingerprint is insufficient. + +The extension belongs to Eventlog's ConsistentTenantCapture contract because ER cannot access the private SQLite connection or prove before/after append continuity through current methods. ER must additionally avoid whole-model clones and whole-history verification scans when advancing a verified model. Approximate scope is 4–6 provider files and 5–8 ER files, plus both specifications and tests, not a facade-only patch. + +An operator question is pending: may the warm cache trust SQLite change tracking after full verification on open, including detection of independent SQL tampering, or must every read detect raw file-byte edits bypassing SQLite? Elapsed time is not approval of the narrower boundary. Existing full-verification behavior stays in force meanwhile. + +Even the warm design cannot make complete fresh open or full growing-history output constant cost. The consumer invoke benchmark must distinguish those costs and retained-handle operation. A claim of complete #51 resolution requires both provider evidence and the actual consumer acceptance measurement. + +Sources: Eventlog eventlog-sqlite/src/lib.rs:48-65 (private connection), :1975-2043 (insufficient snapshot generations), eventlog-sqlite/src/atomic_group.rs:135-193 (transaction boundary); ER adapter.rs:1023-1090,1280-1350,3910-3965 and entity-store/src/asynchronous/verify.rs:765-811 (remaining model/history work). + +## Accepted integrity boundary and resumed scope + +Operator answer on 2026-10-03: Accept SQLite change tracking. Full verification on open remains mandatory; warm cache reuse may rely on SQLite-mediated change detection, including writes and tampering from other connections. Direct database-file byte edits bypassing SQLite are outside that warm guarantee. This is an explicit contract choice, not evidence that an implementation already meets it. + +Implementation spans a generic optional Eventlog capture checkpoint/acknowledged append-delta capability, incremental Entity Runtime verification without repeated whole-model/history copies, and facade multi-subject reads. Unsupported providers fall back to complete capture. New checkpoints are ephemeral, provider-instance and request-bound, never persisted or signed evidence. Changed SQLite state without a proven contiguous append delta requires a complete verified capture. Exact provider API and scenario names will be recorded after scope review, before implementation. + +The approved delivery remains one Entity Runtime integration branch and one PR. Necessary upstream Eventlog support is isolated on fix/er-51-capture-checkpoints based on 06c1e99c86c7130169c7ed584ec7326f7e9a546e and will be bot-published as an exact dependency commit, without a second PR, main merge or release. + +## Accepted implementation design + +# Issue 51 runtime design + +Accepted boundary: full verification on every open. Existing constructors retain FullVerification. Explicit ProviderTracked permits provider-owned proof that SQL-visible content has not changed or has gained a complete acknowledged append suffix. SQL mutation through any connection invalidates unaccounted state; raw file writes bypassing SQLite are excluded from the warm guarantee. Missing capability or invalidation means complete verification, not acceptance from a head fingerprint. + +Public API: exported transient CapturePolicy::{FullVerification,ProviderTracked}; EventlogRecordedStore::open_with_policy; RecordedProviderFacade::start_with_read_policy; bridge equivalent constructor. The existing constructors delegate to FullVerification. A scoped facade view offers load_recorded, read_history, read_histories, lookup_record and lookup_batch; multi-subject histories use one worker request and one read scope, preserving input order and duplicate positions. Complete reads and full snapshot output remain available. No persisted format changes. + +Provider contract: ConsistentTenantCapture::capture_tenant_since(tenant,projections,limits,previous:Option<&CaptureCheckpoint>) returns Complete{capture,checkpoint}, Unchanged{checkpoint}, or AppendDelta{checkpoint,delta}. Immutable checkpoint is opaque and provider/connection/tenant/generation/spec/limits bound. Delta holds exact ordered new events, newly bound blobs, complete before/after projection key mutations, and exact resulting usage. Provider supplies all of a new batch; gaps, external SQL changes, uncertain writes and expired journal force Complete. Normal ER batches already use guarded atomic append with blobs. + +Runtime: retain one mutable verified cache behind a short synchronous lock, never across IO; the checkpoint has a local generation for racing refresh checks. Store a verified model, exact usage, retained admitted blobs/projection rows, and per-subject verified terminal and last position. Scoped answers copy only requested terminal/lookup/history outputs under the lock. Shared batch pre-read retains a compact operation view. Do not clone the entire model on ordinary append, compare complete captures, rewalk batch closure, rescan old histories, or rebuild complete expected projection sets. + +Bounded suffix verifier: fast path only for linear er.recorded_entry events whose previous verified prefixes and origins are attested unchanged. Reuse event/blob admission and whole-new-batch construction. Check positions, unique record/batch identities, and exact new receipts; replay each suffix entry against the cached predecessor with validate_entry_against_state. Compare every changed projection row to exact per-key expected content and its verified previous value. Install checkpoint only after successful validation. Imports, lineage, nonordinary changes and uncertainty use complete fallback. On an unsuccessful tentative advance discard it and ask the complete verifier for the authoritative refusal. + +Atomicity: a caller cannot observe a partly advanced model. A concurrent cache advance invalidates an in-flight local generation and retries. Provider checkpoint proof is fresh at its capture transaction; the existing append guard and post-commit verification preserve the existing race semantics. No new claim that all historical damage racing a commit prevents that commit. + +Verification: red shared-clock fixture at 55/601/1203 events, representative embedded definitions; unchanged reads and fixed-size batches measured separately from open/output cloning. Real public facade timing must meet 2x before a performance claim. Provider counters must show no old events/blobs/rows for unchanged and only new suffix verification for acknowledged append. Differential full-vs-tracked results and corrupt suffix mutations must match. External SQL old-blob/event/row/binding changes with stable event head must invalidate and refuse exactly as full verification. Token expiry, wrong scope/limits/generation, imports, lineage and concurrent reader coverage remain mandatory. + +State of this file: accepted implementation direction, not verification evidence. ESS declaration is a typed transient coordinate only; coordinator adds executable provider scenarios through the real adapter. No production changes or build are authorized until root admission. + +## Provider conformance execution plan + +The standalone ESS entry point ess/provider-tracking declares the transient CapturePolicy enum before source implementation. Coordinator expands it with real SQLite/facade command adapters in the existing checker under optional feature eventlog and --spec-root. The original five-library suite and Rust1.85 path remain separate; the provider suite uses Rust1.91. Compiled CapturePolicy enum must match the complete Rust variant set. Seven authored scenarios are validated in coordinator scratch: unchanged-head-blob-tamper, unchanged-head-event-tamper, unchanged-head-projection-tamper, unchanged-head-identity-tamper, unchanged-head-delete-blob-tamper, multi-subject-histories-one-scope, unchanged-capture-retains-verified-state. Generated and authored cases must all execute; validation alone is not conformance. Performance is separately measured by the real public facade release probe at55/601/1203 exact event counts, with raw medians and <=2x gate. + +## Measured scope and review progress + +The earlier reference to an actual consumer invocation is superseded by the accepted public-facade probe: consumer adoption and repins are excluded from this delivery. The release-mode fixture uses real SQLite, canonical Entity Runtime records, the public facade, 55/601/1203 exact events and a clock shared by every historical batch. Opening and full-history output are measured separately from warm bounded batches. Baseline medians 92.517484/7038.153041/12449.723852 ms fail the unchanged <=2x assertion (exit101); tracked treatment 10.511861/11.983571/17.701283 ms passes (exit0), growth1.140/1.684x. Unchanged reads cause no extra capture/decode/model work. Raw outputs, source diff and hashes remain in the unit's perf scratch until evidence admission. + +Provider focused tests pass195, including19 new controls, with four compile-valid defects caught and restored. Final upstream/integration gates and independent reviews are still pending. Runtime adversary found a receipt-coordinate parity gap between delta and full verification; implementation is being corrected before acceptance. These intermediate results do not clear the provider blocker or finish the story. + +## Scope + +The initial inferred provider extension is now implemented, replacing the earlier facade-only feasibility estimate. Runtime unit1516e748 changes adapter.rs, adapter/scoped.rs, new adapter/tracked.rs and its independent review_tests.rs, facade.rs, sync.rs and lib.rs; shared_clock_cost.rs is the explicitly run release performance probe; the provider policy has its own ESS root. Actual new runtime tests are five implementor cases plus three adversary cases; library66→74 and all-feature package186 passed, with the separately executed performance probe ignored by ordinary package selection. Receipt parity was an introduced defect, reproduced through a native SQLite writer, fixed by bounded receipt-key validation, and independently rechecked without weakening assertions. + +Coordinator surfaces are checks/ess-conformance (real SQLite/facade adapter and selected spec root), ess/provider-tracking (typed policy, ten adapter commands and seven authored scenarios), Taskfile.yml (required provider contract lane), AGENTS.md and docs/ess/README.md (gate contract), requirements R-151, two provider designs, dependency manifests/locks, changelog and AEP evidence. The previous five-library scenario contracts remain authority and must remain unchanged. Upstream Eventlog core/SQLite work is separately governed by story:sqlite-tracked-capture and its independent review; delivery consumes one exact published revision. + +## Final integrated acceptance + +Eventlog6983cc25eb92e07844b3a6fa3e0decbdb300f43c is published on fix/er-51-capture-checkpoints with signed common checks. All runtime/checker manifests and lockfiles use that exact Git revision, with no path override. Final task check exits0: formatting, workspace lint/tests, docs, examples, requirements, fixture pins, real PostgreSQL, all-feature Eventlog runtime tests/lint, notes, original ESS and provider ESS all complete. Runtime186 passed; its timing probe was deliberately ignored in the package selection and then separately executed. Original ESS421/421 and provider ESS17/17 pass without failed/error/unsupported/skipped cases; all original421 scenario contract bytes remain unchanged from the prior integration. + +The final release probe exits0 with one actual test: medians11.532148/13.049370/13.568977ms at55/601/1203 events, growth1.132/1.177x under the unchanged2x assertion. It followed all review corrections and used the published provider revision. Its exact stdout and source/Cargo hashes are public under docs/ess/evidence/provider-tracking. The original development benchmark and final integrated check are distinct observations, not selected repeats of one measurement. Upstream full/proof gates execute545 tests with zero skips. The dependency blocker is cleared and all introduced review findings are fixed. + +Five asynchronous test bodies were wrapped in conventional #[test] entrypoints with an identical current-thread runtime because the legacy requirement checker recognizes that form. Names and assertions were preserved; final gate includes them, and all125 requirements have live evidence. No Python checker change was made. Agent token/tool/wall accounting was not supplied by the harness and remains unknown. Delivery is one Entity Runtime PR; no upstream PR, main merge, release or consumer repin. diff --git a/.engineering/planning/story/declared-refusal-before-fulfillment-validation.md b/.engineering/planning/story/declared-refusal-before-fulfillment-validation.md new file mode 100644 index 00000000..3f8f21a3 --- /dev/null +++ b/.engineering/planning/story/declared-refusal-before-fulfillment-validation.md @@ -0,0 +1,72 @@ +--- +format: aep.planning-md/3 +id: story:declared-refusal-before-fulfillment-validation +kind: story +status: implemented +title: Preserve declared refusals when success fulfillment keys are supplied +refs: +- provider: github + reference: beyond10x/entity-runtime#49 +relations: +- serves: vision:O2 +- informed_by: story:service-binding-boundary +scope: +- confidence: cited + path: CHANGELOG.md +- confidence: cited + path: crates/entity-executor/src/lib.rs +- confidence: cited + path: crates/entity-executor/tests/refusal_fulfillment_review.rs +- confidence: cited + path: crates/entity-executor/tests/service_3_fulfillment_retry.rs +- confidence: cited + path: docs/ess/executor-traceability.md +- confidence: cited + path: docs/requirements.md +- confidence: cited + path: ess/coverage.json +- confidence: cited + path: ess/ess-inputs.yaml +- confidence: cited + path: ess/generated/model.json +- confidence: cited + path: ess/generated/suite.json +- confidence: cited + path: ess/scenarios/executor/refusal-ignores-success-fulfillments.yaml +- confidence: cited + path: ess/scenarios/executor/subject-refusal-ignores-success-fulfillments.yaml +revision: 13 +transitions: +- {from: "draft", to: "proposed", at: "2026-10-03T14:50:47Z", actor: "human:timo", revision: 5} +- {from: "proposed", to: "active", at: "2026-10-03T14:50:47Z", actor: "human:timo", revision: 6} +- {from: "active", to: "implemented", at: "2026-10-03T15:28:40Z", actor: "human:timo", revision: 13, decided_on: {"recorded":{"test_result":2,"verification":1,"ess_conformance_coverage_v1":1}}} +--- +## Context + +GitHub issue #49 reports that service guard refusals are replaced by FulfillmentKeysMismatch when a caller supplies success-outcome keys. The request is https://github.com/beyond10x/entity-runtime/issues/49; the relevant check is crates/entity-core/src/runtime.rs. + +## Acceptance + +Named conformance scenarios `refusal-ignores-success-fulfillments` and `subject-refusal-ignores-success-fulfillments` return the selected declared outcome and error unchanged when success fulfillment keys are supplied, while successful decisions still enforce their exact fulfillment contract and refusals mutate no caller state. These are the repository-conventional hyphenated forms of the initially proposed scenario names; Rust regressions retain underscore names. + +## Delivery + +One issue-fix integration branch and one PR with issues #50 and #51, as requested by the operator. Regression tests precede implementation; preserve deterministic replay and record ESS evidence. + +## Scope + +Final scope confirmed by implementation commit c6164443, review-result:issue-49-adversary-public and the integrated conformance report. + +- crates/entity-executor/src/lib.rs: restrict extra fulfillment-key validation to accepted completed evaluations; keep declared Refused intact — cited. +- crates/entity-executor/tests/service_3_fulfillment_retry.rs: three new input/stored-refusal and accepted-key regression cases — cited. +- crates/entity-executor/tests/refusal_fulfillment_review.rs: two adversarial rollback, malformed-action and retry cases — cited. +- ess/scenarios/executor/refusal-ignores-success-fulfillments.yaml and subject-refusal-ignores-success-fulfillments.yaml: originally inferred new paths, now authored and executed — cited. +- Coordinator ess/ess-inputs.yaml, ess/generated/suite.json, ess/coverage.json, docs/requirements.md (R-152), docs/ess/executor-traceability.md and CHANGELOG.md — cited. +- Correction: no kernel change was needed; the kernel already returned the declared refusal. Generated model.json required no byte change because existing request and observation types express the regression. +- Collision with #50 required serial executor edits and coordinator-owned common artifacts. + +Measured evidence: original package red reproduced FulfillmentKeysMismatch; package count 17 to 20 after the fix and 20 to 22 after independent review. Conformance count 414 to 416 passed with no changed prior contracts, followed by 421/421 after #50 integration. Refusals preserve exact details and state/history/receipt absence; accepted outcomes retain exact keys. + +## Finding + +The kernel already preserves declared refusals. The remaining defect is in the executor, including subject-dependent selectors. Input-only refusals already return early there; the regression must exercise a stored-field guard. Existing conformance Failure::debug carries the exact outcome/error in its detail, so no new error schema is needed. diff --git a/.engineering/planning/story/executor-input-refusal-before-existence.md b/.engineering/planning/story/executor-input-refusal-before-existence.md new file mode 100644 index 00000000..6c9c82a2 --- /dev/null +++ b/.engineering/planning/story/executor-input-refusal-before-existence.md @@ -0,0 +1,106 @@ +--- +format: aep.planning-md/3 +id: story:executor-input-refusal-before-existence +kind: story +status: implemented +title: Evaluate executor input refusals before row existence +refs: +- provider: github + reference: beyond10x/entity-runtime#50 +relations: +- serves: vision:O2 +- informed_by: story:service-binding-boundary +scope: +- confidence: cited + path: CHANGELOG.md +- confidence: cited + path: checks/ess-conformance/src/executor.rs +- confidence: cited + path: crates/entity-executor/src/lib.rs +- confidence: cited + path: crates/entity-executor/tests/input_refusal_precedence.rs +- confidence: cited + path: crates/entity-executor/tests/version_binding_review.rs +- confidence: cited + path: docs/design/recorded-execution-v0.1.md +- confidence: cited + path: docs/ess/executor-traceability.md +- confidence: cited + path: docs/requirements.md +- confidence: cited + path: ess/coverage.json +- confidence: cited + path: ess/ess-inputs.yaml +- confidence: cited + path: ess/generated/model.json +- confidence: cited + path: ess/generated/suite.json +- confidence: cited + path: ess/scenarios/executor/ +revision: 13 +transitions: +- {from: "draft", to: "proposed", at: "2026-10-03T14:50:47Z", actor: "human:timo", revision: 5} +- {from: "proposed", to: "active", at: "2026-10-03T14:50:47Z", actor: "human:timo", revision: 6} +- {from: "active", to: "implemented", at: "2026-10-03T15:28:40Z", actor: "human:timo", revision: 13, decided_on: {"recorded":{"test_result":1,"review_outcome":1,"verification":1,"ess_conformance_coverage_v1":1}}} +--- +## Context + +GitHub issue #50 reports RevisionConflict hides an input-guarded declared refusal for creation over an existing row and execution against a missing row: https://github.com/beyond10x/entity-runtime/issues/50. entity-core already exposes decide_before_load; entity-executor decides row existence in its execution path. + +## Acceptance + +Named conformance scenarios executor-input-refusal-existing-create and executor-input-refusal-missing-execute return the declared input refusal before row existence checks, append nothing, and preserve successful execution, revision-conflict, and batch atomicity behavior. Explicit versions are required for pre-load execution; legacy APIs preserve row-derived authority. The scenario identifiers use ESS hyphens; the Rust regression function identifiers retain underscores. + +## Delivery + +One issue-fix integration branch and one PR with issues #49 and #51. Regression tests precede implementation and ESS evidence accompanies the fix. + +## Scope + +Final scope confirmed by the implementor report and independent review of commit be0ce7e4; coordinator integration changes are named below. + +- crates/entity-executor/src/lib.rs: explicit version-bound APIs, prepared action reuse, creation refusal precedence and exact historical version checks — cited. +- crates/entity-executor/tests/input_refusal_precedence.rs: previously inferred new file, confirmed absent before implementation and added with 13 passing regressions, including two original red reproductions — cited. +- crates/entity-executor/tests/version_binding_review.rs: independent review added three passing authority, byte-identity and atomicity probes — cited. +- checks/ess-conformance/src/executor.rs: optional explicit version parsing; malformed versions refuse before polling — cited. +- Five new files in ess/scenarios/executor/issue-50-*.yaml plus coordinator ess/ess-inputs.yaml, ess/generated/model.json, ess/generated/suite.json and ess/coverage.json — cited. +- CHANGELOG.md, docs/design/recorded-execution-v0.1.md, docs/ess/executor-traceability.md and docs/requirements.md — cited coordinator changes. +- Correction: docs/design/service-binding-boundary-v0.1.md was scoped conservatively but required no change; the accepted executor boundary is documented in recorded-execution-v0.1.md. +- Collision with #49 was resolved by serial unit implementation and coordinator-owned common ESS artifacts. + +Evidence: review-result:issue-50-adversary; package 22 to 35 implementor cases, then 35 to 38 adversarial cases, all green. Integrated ESS report executes 421/421 with no skipped or unsupported scenarios; all 416 earlier scenario contracts are unchanged. First generation refused two underscore scenario names; correcting identifiers to ESS hyphen syntax did not change assertions. + +## Proposed design decision + +Creation already has an explicit definition_version: call the kernel's decide_create and preserve Refused before checking existing state. Missing-subject execution requires a caller-selected definition version: ExecuteRequest currently has no version, and Registry::versions admits several. Recommend an additive version-bound execution entry point, preserving the existing API's row-derived version semantics rather than silently selecting latest. Reassess callers, request recovery identity and ESS declarations before implementation. This recommendation is not yet an accepted contract. + +## Accepted execution design + +## Explicit definition versions before loading + +For fresh creates, evaluate the declared creation refusal before loading the subject. Preserve +existing conflict precedence for other creation errors and retain request recovery before fresh +decision. Creation already names its definition version. + +For execution against subjects that may be absent, the caller selects a positive definition +version through additive `Executor::execute_versioned` and `Executor::batch_versioned` methods. +`VersionedBatchAction` carries a version with Execute and Merge while retaining Create and Observe. +The existing ExecuteRequest and BatchAction retain their source-compatible shapes and row-derived +version semantics. Neither API implicitly selects the latest or sole registered version. + +The versioned path validates request shape, recovers existing record/batch identities, then runs +`decide_before_load` for each fresh versioned action before that action's state/history read. +A declared input refusal wins over existence, revision and merge-history checks; a request that +needs loaded state retains ordinary conflict checks. The prepared definition must match a loaded +instance. Preserve per-member batch decision order and append only after all members succeed. + +Retries compare an explicit requested version with the saved record's definition/version before +reusing the existing canonical request comparison. Exact retries still work after clearing the +registry; a different explicit version conflicts. No `er.request/*` framing changes. Refusal +recording retains explicit caller version. Conformance Execute/Batch request documents support +optional definition_version at the adapter boundary, with real calls to the versioned APIs. + +Required scenarios cover refusal for existing create and absent execution, no load on input +refusal, accepted missing/stale conflicts, loaded version mismatch, mixed-batch rollback and +committed/imported retry version identity. These are behavior over the existing +entity.executor.RequestDocument contract, not a new persisted entity. diff --git a/.engineering/planning/task/release-0-26-0.md b/.engineering/planning/task/release-0-26-0.md new file mode 100644 index 00000000..754897a1 --- /dev/null +++ b/.engineering/planning/task/release-0-26-0.md @@ -0,0 +1,30 @@ +--- +format: aep.planning-md/3 +id: task:release-0-26-0 +kind: task +status: implemented +title: Prepare the 0.26.0 release candidate for issues 49–51 +relations: +- delivers: design:github-issues-integration-wave +- serves: vision:O2 +revision: 4 +transitions: +- {from: "draft", to: "proposed", at: "2026-10-03T18:32:49Z", actor: "human:timo", revision: 2} +- {from: "proposed", to: "active", at: "2026-10-03T18:32:49Z", actor: "human:timo", revision: 3} +- {from: "active", to: "implemented", at: "2026-10-03T18:39:22Z", actor: "human:timo", revision: 4, decided_on: {"recorded":{"test_result":1}}} +--- +## Outcome + +The operator requested merge and a new version after the issue-fix wave passed. Prepare 0.26.0 in the same PR (#52), then deliver it through the repository bot release process. A minor version reflects the additive explicit-version executor and provider-tracked capture APIs. + +## Scope + +Update workspace and path-dependency versions, both lockfiles, and the dated changelog section. No new runtime behavior or specification entities are introduced. + +## Acceptance + +The full `task check` passes against a real PostgreSQL fixture with both ESS suites. PR #52 retains all required green checks before its exact candidate is merged. Release publication is separately observable at https://github.com/beyond10x/entity-runtime/releases/tag/0.26.0: the annotated tag must be reachable from main, its release workflow must succeed, and the bot must publish all five platform archives and their verified checksums. + +## Delivery boundary + +This task records release preparation; GitHub records the subsequent merge, tagged build, and publication. A pushed tag alone is queued, not released. Public documentation delivery proceeds asynchronously. diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index 106671ab..9086545d 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -156,3 +156,18 @@ jobs: - name: Lint every Eventlog provider and bridge run: cargo clippy -p entity-eventlog --all-targets --all-features --no-deps --locked -- -D warnings + + - name: Executable SQLite provider contracts + run: | + cargo clippy --manifest-path checks/ess-conformance/Cargo.toml --features eventlog --all-targets --locked -- -D warnings + cargo run --manifest-path checks/ess-conformance/Cargo.toml --features eventlog --locked -- --spec-root ess/provider-tracking check --reports target/provider-ess-conformance + + - name: Retain exact provider conformance evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: er-provider-ess-conformance + path: | + target/provider-ess-conformance/ + ess/provider-tracking/generated/ + ess/provider-tracking/coverage.json diff --git a/AGENTS.md b/AGENTS.md index d9528bc1..c482ea92 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -173,7 +173,8 @@ what it records, in both directions — a moved copy and an unpinned file beside against the server `ENTITY_POSTGRES_URL` names, or one printed line saying they did not run) · `eventlog-runtime-check` (`entity-eventlog`'s tests and Clippy with `--all-features` on the `+1.91.0` toolchain) · `notes-check` · `ess-check` (canonical regeneration, reviewed scenario -contracts and complete execution against the selected libraries). Every cargo step runs `--locked`, so the gate judges the dependency +contracts and complete execution against the selected libraries) · `provider-ess-check` +(the separate SQLite tracking/facade contract on Rust 1.91). Every cargo step runs `--locked`, so the gate judges the dependency set the repository committed rather than one cargo re-resolved on the way past. The local `pin-check` holds the AEP lifecycle fixture against its own `PIN.md`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 19fd96d4..4e970316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ Every change a user of the runtime sees, per release. Unreleased work sits at th ## [Unreleased] +## [0.26.0] — 2026-10-03 + +### Added + +- Opt-in `CapturePolicy::ProviderTracked` reuses a fully verified provider observation when + SQLite proves it is unchanged, or verifies only a proven append suffix. SQL changes from + other connections invalidate reuse. Every open still verifies the complete store; raw file + edits bypassing SQLite are outside the warm tracking guarantee. +- Scoped recorded-facade reads, including one-call `read_histories` that preserves requested + order and duplicate subjects. Existing constructors retain full verification. +- `Executor::execute_versioned` and `batch_versioned` let callers select an exact definition + version and receive input-guarded refusals before subject existence or revision checks. + Existing execution methods keep selecting the definition from the loaded subject. + +### Fixed + +- Preserve a declared service refusal when a command supplies fulfillment keys for its success + outcome, including guards that inspect the loaded subject. +- Return a declared creation refusal before an existing-subject conflict. Exact retries still + recover their original result before evaluating a new decision. + ## [0.25.1] — 2026-09-28 ### Added diff --git a/Cargo.lock b/Cargo.lock index ba2cdd8b..cdf9fb67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,7 +404,7 @@ dependencies = [ [[package]] name = "entity-cli" -version = "0.25.1" +version = "0.26.0" dependencies = [ "clap", "entity-core", @@ -425,7 +425,7 @@ dependencies = [ [[package]] name = "entity-core" -version = "0.25.1" +version = "0.26.0" dependencies = [ "scan-support", "serde", @@ -434,7 +434,7 @@ dependencies = [ [[package]] name = "entity-eventlog" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-executor", @@ -455,7 +455,7 @@ dependencies = [ [[package]] name = "entity-executor" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-store", @@ -464,7 +464,7 @@ dependencies = [ [[package]] name = "entity-graph" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "scan-support", @@ -473,7 +473,7 @@ dependencies = [ [[package]] name = "entity-mcp" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-shell", @@ -485,7 +485,7 @@ dependencies = [ [[package]] name = "entity-postgres" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-eventlog", @@ -506,7 +506,7 @@ dependencies = [ [[package]] name = "entity-query" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-store", @@ -516,7 +516,7 @@ dependencies = [ [[package]] name = "entity-remote" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-store", @@ -526,7 +526,7 @@ dependencies = [ [[package]] name = "entity-shell" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-store", @@ -535,7 +535,7 @@ dependencies = [ [[package]] name = "entity-sqlite" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-eventlog", @@ -550,7 +550,7 @@ dependencies = [ [[package]] name = "entity-store" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "fs2", @@ -560,7 +560,7 @@ dependencies = [ [[package]] name = "entity-surface" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-graph", @@ -571,14 +571,14 @@ dependencies = [ [[package]] name = "entity-xtask" -version = "0.25.1" +version = "0.26.0" dependencies = [ "clap", ] [[package]] name = "entity-yaml" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "serde", @@ -605,7 +605,7 @@ dependencies = [ [[package]] name = "eventlog-core" version = "0.6.0" -source = "git+https://github.com/beyond10x/eventlog?rev=0a0484634e8c640be29d6b6541d6cc1c1aaef7d3#0a0484634e8c640be29d6b6541d6cc1c1aaef7d3" +source = "git+https://github.com/beyond10x/eventlog?rev=6983cc25eb92e07844b3a6fa3e0decbdb300f43c#6983cc25eb92e07844b3a6fa3e0decbdb300f43c" dependencies = [ "serde", "serde_json", @@ -618,7 +618,7 @@ dependencies = [ [[package]] name = "eventlog-file" version = "0.6.0" -source = "git+https://github.com/beyond10x/eventlog?rev=0a0484634e8c640be29d6b6541d6cc1c1aaef7d3#0a0484634e8c640be29d6b6541d6cc1c1aaef7d3" +source = "git+https://github.com/beyond10x/eventlog?rev=6983cc25eb92e07844b3a6fa3e0decbdb300f43c#6983cc25eb92e07844b3a6fa3e0decbdb300f43c" dependencies = [ "eventlog-core", "serde", @@ -631,7 +631,7 @@ dependencies = [ [[package]] name = "eventlog-postgres" version = "0.6.0" -source = "git+https://github.com/beyond10x/eventlog?rev=0a0484634e8c640be29d6b6541d6cc1c1aaef7d3#0a0484634e8c640be29d6b6541d6cc1c1aaef7d3" +source = "git+https://github.com/beyond10x/eventlog?rev=6983cc25eb92e07844b3a6fa3e0decbdb300f43c#6983cc25eb92e07844b3a6fa3e0decbdb300f43c" dependencies = [ "eventlog-core", "rustls", @@ -648,7 +648,7 @@ dependencies = [ [[package]] name = "eventlog-sqlite" version = "0.6.0" -source = "git+https://github.com/beyond10x/eventlog?rev=0a0484634e8c640be29d6b6541d6cc1c1aaef7d3#0a0484634e8c640be29d6b6541d6cc1c1aaef7d3" +source = "git+https://github.com/beyond10x/eventlog?rev=6983cc25eb92e07844b3a6fa3e0decbdb300f43c#6983cc25eb92e07844b3a6fa3e0decbdb300f43c" dependencies = [ "eventlog-core", "nix", @@ -661,7 +661,7 @@ dependencies = [ [[package]] name = "eventlog-tree" version = "0.6.0" -source = "git+https://github.com/beyond10x/eventlog?rev=0a0484634e8c640be29d6b6541d6cc1c1aaef7d3#0a0484634e8c640be29d6b6541d6cc1c1aaef7d3" +source = "git+https://github.com/beyond10x/eventlog?rev=6983cc25eb92e07844b3a6fa3e0decbdb300f43c#6983cc25eb92e07844b3a6fa3e0decbdb300f43c" dependencies = [ "eventlog-core", "eventlog-sqlite", @@ -1679,7 +1679,7 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "scan-support" -version = "0.25.1" +version = "0.26.0" [[package]] name = "scopeguard" diff --git a/Cargo.toml b/Cargo.toml index 225e3379..fea47561 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ ] [workspace.package] -version = "0.25.1" +version = "0.26.0" edition = "2021" license = "Apache-2.0" repository = "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/beyond10x/entity-runtime" diff --git a/Taskfile.yml b/Taskfile.yml index a901e2c1..8ab50eb3 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -16,6 +16,15 @@ tasks: - task: eventlog-runtime-check - task: notes-check - task: ess-check + - task: provider-ess-check + + provider-ess-check: + desc: SQLite tracked-provider contract against real facade calls and external SQL mutations + env: + CARGO_TARGET_DIR: '{{.ER_ESS_TARGET_DIR | default "checks/ess-conformance/target"}}' + cmds: + - cargo +1.91.0 clippy --manifest-path checks/ess-conformance/Cargo.toml --features eventlog --all-targets --locked -- -D warnings + - cargo +1.91.0 run --manifest-path checks/ess-conformance/Cargo.toml --features eventlog --locked -- --spec-root ess/provider-tracking check --reports target/provider-ess-conformance ess-check: desc: Deterministic ESS compilation, reviewed scenario coverage and actual library conformance diff --git a/checks/ess-conformance/Cargo.lock b/checks/ess-conformance/Cargo.lock index 406631f9..2a445f5a 100644 --- a/checks/ess-conformance/Cargo.lock +++ b/checks/ess-conformance/Cargo.lock @@ -76,12 +76,34 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cc" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "clap" version = "4.6.7" @@ -171,6 +193,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + [[package]] name = "digest" version = "0.10.7" @@ -200,15 +231,32 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "entity-core" -version = "0.25.1" +version = "0.26.0" dependencies = [ "serde", "serde_json", ] +[[package]] +name = "entity-eventlog" +version = "0.26.0" +dependencies = [ + "entity-core", + "entity-executor", + "entity-query", + "entity-store", + "eventlog-core", + "eventlog-sqlite", + "serde", + "serde_json", + "sha2 0.10.9", + "time", + "tokio", +] + [[package]] name = "entity-executor" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-store", @@ -217,7 +265,7 @@ dependencies = [ [[package]] name = "entity-query" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-store", @@ -227,7 +275,7 @@ dependencies = [ [[package]] name = "entity-shell" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "entity-store", @@ -236,7 +284,7 @@ dependencies = [ [[package]] name = "entity-store" -version = "0.25.1" +version = "0.26.0" dependencies = [ "entity-core", "fs2", @@ -256,6 +304,7 @@ version = "0.1.0" dependencies = [ "clap", "entity-core", + "entity-eventlog", "entity-executor", "entity-query", "entity-shell", @@ -264,10 +313,13 @@ dependencies = [ "ess-conformance", "ess-domain", "ess-primitives", + "eventlog-core", + "rusqlite", "serde", "serde_json", "serde_yaml_ng", "sha2 0.10.9", + "time", ] [[package]] @@ -337,6 +389,56 @@ dependencies = [ "thiserror", ] +[[package]] +name = "eventlog-core" +version = "0.6.0" +source = "git+https://github.com/beyond10x/eventlog?rev=6983cc25eb92e07844b3a6fa3e0decbdb300f43c#6983cc25eb92e07844b3a6fa3e0decbdb300f43c" +dependencies = [ + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror", + "time", + "uuid", +] + +[[package]] +name = "eventlog-sqlite" +version = "0.6.0" +source = "git+https://github.com/beyond10x/eventlog?rev=6983cc25eb92e07844b3a6fa3e0decbdb300f43c#6983cc25eb92e07844b3a6fa3e0decbdb300f43c" +dependencies = [ + "eventlog-core", + "nix", + "rusqlite", + "serde_json", + "time", + "tokio", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "fs2" version = "0.4.3" @@ -347,6 +449,30 @@ dependencies = [ "winapi", ] +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -357,11 +483,43 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" +dependencies = [ + "hashbrown 0.17.1", +] [[package]] name = "heck" @@ -385,7 +543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.17.1", ] [[package]] @@ -400,24 +558,88 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "libsqlite3-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.107" @@ -454,6 +676,43 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rsqlite-vfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1", + "thiserror", +] + +[[package]] +name = "rusqlite" +version = "0.40.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "ryu" version = "1.0.23" @@ -586,6 +845,36 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" + +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + [[package]] name = "strsim" version = "0.11.1" @@ -634,6 +923,57 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tokio" +version = "1.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" +dependencies = [ + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "typenum" version = "1.20.1" @@ -664,12 +1004,74 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.6", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/checks/ess-conformance/Cargo.toml b/checks/ess-conformance/Cargo.toml index e04d5a86..f1b6d0a6 100644 --- a/checks/ess-conformance/Cargo.toml +++ b/checks/ess-conformance/Cargo.toml @@ -8,7 +8,16 @@ rust-version = "1.85" # Verification tooling is deliberately outside the production workspace and dependency graph. [workspace] +[features] +# The provider is a separate Rust 1.91 contract; the five-library checker retains Rust 1.85. +eventlog = ["dep:entity-eventlog", "dep:eventlog-core", "dep:rusqlite", "dep:time"] + [dependencies] +entity-eventlog = { path = "../../crates/entity-eventlog", features = ["sqlite", "sync-bridge"], optional = true } +eventlog-core = { git = "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/beyond10x/eventlog", rev = "6983cc25eb92e07844b3a6fa3e0decbdb300f43c", optional = true } +# SQL mutation fixtures exercise a second connection; they never substitute provider observations. +rusqlite = { version = "=0.40.2", features = ["bundled"], optional = true } +time = { version = "0.3", optional = true } entity-core = { path = "../../crates/entity-core" } entity-store = { path = "../../crates/entity-store" } entity-executor = { path = "../../crates/entity-executor" } diff --git a/checks/ess-conformance/build.rs b/checks/ess-conformance/build.rs index d3ffa84d..ce8a9f30 100644 --- a/checks/ess-conformance/build.rs +++ b/checks/ess-conformance/build.rs @@ -7,7 +7,7 @@ fn main() -> Result<(), Box> { ) .join("../..") .canonicalize()?; - for name in ["core", "store", "executor", "shell", "query"] { + for name in ["core", "store", "executor", "shell", "query", "eventlog"] { println!( "cargo:rerun-if-changed={}", root.join(format!("crates/entity-{name}/src")).display() diff --git a/checks/ess-conformance/src/executor.rs b/checks/ess-conformance/src/executor.rs index b8c0ad06..3fe8f973 100644 --- a/checks/ess-conformance/src/executor.rs +++ b/checks/ess-conformance/src/executor.rs @@ -9,6 +9,7 @@ use entity_core::{EntityDefinition, EntityInstance, Registry}; use entity_executor::{ test_support::{block_on, poll_once}, BatchAction, CreateRequest, ExecuteRequest, ExecutionError, Executor, MergeRequest, + VersionedBatchAction, }; use entity_store::{ asynchronous::{ @@ -178,9 +179,18 @@ impl Target { "Create" => Ok(store::outcome( block_on(self.executor().create(create(request)?)).map_err(execution_error)?, )), - "Execute" => Ok(store::outcome( - block_on(self.executor().execute(execute(request)?)).map_err(execution_error)?, - )), + "Execute" => { + let result = + if request.get("definition_version").is_some() { + block_on(self.executor().execute_versioned( + execute(request)?, + read(request, "definition_version")?, + )) + } else { + block_on(self.executor().execute(execute(request)?)) + }; + Ok(store::outcome(result.map_err(execution_error)?)) + } "Observe" => Ok(store::outcome( block_on( self.executor() @@ -188,16 +198,23 @@ impl Target { ) .map_err(execution_error)?, )), - "Batch" => Ok(store::outcome( - block_on( - self.executor() - .batch(read(request, "key")?, actions(request)?), - ) - .map_err(execution_error)?, - )), + "Batch" => Ok(store::outcome(block_on(self.run_batch(request))?)), "CancelBatch" => { + let key = read(request, "key")?; + let versioned = versioned_actions(request)?; + let legacy = if versioned.is_none() { + actions(request)? + } else { + Vec::new() + }; let executor = self.executor(); - let mut future = Box::pin(executor.batch(read(request, "key")?, actions(request)?)); + let mut future = Box::pin(async move { + match versioned { + Some(actions) => executor.batch_versioned(key, actions).await, + None => executor.batch(key, legacy).await, + } + .map_err(execution_error) + }); if !read::(request, "poll")? { drop(future); return Ok(json!({"poll":"Unpolled"})); @@ -207,9 +224,9 @@ impl Target { drop(future); Ok(json!({"poll":"Pending"})) } - Poll::Ready(result) => Ok( - json!({"poll":"Ready","outcome":store::outcome(result.map_err(execution_error)?)}), - ), + Poll::Ready(result) => { + Ok(json!({"poll":"Ready","outcome":store::outcome(result?)})) + } } } "Load" => serialize(block_on(self.ports.load(&subject(request)?))?), @@ -331,16 +348,24 @@ impl Target { )), } } + async fn run_batch(&self, request: &Value) -> Result { + let key = read(request, "key")?; + let result = match versioned_actions(request)? { + Some(actions) => self.executor().batch_versioned(key, actions).await, + None => self.executor().batch(key, actions(request)?).await, + }; + result.map_err(execution_error) + } } -fn execution_error(error: ExecutionError) -> Failure { +pub(super) fn execution_error(error: ExecutionError) -> Failure { match error { ExecutionError::Core(e) => Failure::debug(e), ExecutionError::Store(e) => e.into(), ExecutionError::Write(e) => e.into(), } } -fn create(v: &Value) -> Result { +pub(super) fn create(v: &Value) -> Result { Ok(CreateRequest { subject: subject(v)?, definition_version: read(v, "version")?, @@ -358,7 +383,7 @@ fn execute(v: &Value) -> Result { recording: recording(&v["recording"])?, }) } -fn actions(v: &Value) -> Result> { +pub(super) fn actions(v: &Value) -> Result> { read::>(v, "actions")? .iter() .map(|a| match read::(a, "kind")?.as_str() { @@ -374,6 +399,44 @@ fn actions(v: &Value) -> Result> { .collect() } +fn versioned_actions(v: &Value) -> Result>> { + let raw = read::>(v, "actions")?; + if !raw + .iter() + .any(|action| action.get("definition_version").is_some()) + { + return Ok(None); + } + raw.iter() + .map(|action| { + let kind = read::(action, "kind")?; + match kind.as_str() { + "execute" => Ok(VersionedBatchAction::Execute { + definition_version: read(action, "definition_version")?, + request: execute(action)?, + }), + "merge" => Ok(VersionedBatchAction::Merge { + definition_version: read(action, "definition_version")?, + request: MergeRequest { + execute: execute(action)?, + first: read(action, "first")?, + }, + }), + "create" | "observe" if action.get("definition_version").is_some() => { + Err(Failure::named( + "Deserialize", + "definition_version is only valid for execute or merge", + )) + } + "create" => Ok(VersionedBatchAction::Create(create(action)?)), + "observe" => Ok(VersionedBatchAction::Observe(read(action, "observation")?)), + _ => Err(Failure::named("Deserialize", "unknown batch action")), + } + }) + .collect::>>() + .map(Some) +} + fn reason(reason: &a::RefusalReason) -> Value { match reason { a::RefusalReason::Kernel { message } => json!({"kind":"Kernel","message":message}), diff --git a/checks/ess-conformance/src/identity.rs b/checks/ess-conformance/src/identity.rs index 27f0f743..0399c316 100644 --- a/checks/ess-conformance/src/identity.rs +++ b/checks/ess-conformance/src/identity.rs @@ -28,7 +28,7 @@ pub fn source_identity(root: &Path, mut watch: impl FnMut(&Path)) -> Result Result { Ok(fs::read_to_string(root.join(path))?) } -fn generate(root: &Path) -> Result<(String, String)> { - let ess_root = root.join("ess"); +fn generate(ess_root: &Path) -> Result<(String, String)> { let manifest: Manifest = serde_yaml_ng::from_str(&fs::read_to_string(ess_root.join("ess-inputs.yaml"))?)?; if manifest.format != "ess-inputs/1" { @@ -132,17 +136,19 @@ fn generate(root: &Path) -> Result<(String, String)> { let mut sources = SourceMap::new(); let mut files = Vec::new(); for path in manifest.specification { - let text = read(&ess_root, &path)?; + let text = read(ess_root, &path)?; let raw = RawSpecFile::parse(&text).map_err(|e| format!("{path}: {e}"))?; sources.insert(path.as_str(), text.as_str()); files.push((Source::new(path), raw)); } let spec = Specification::assemble(files).map_err(|e| e.to_string())?; let ir = compile(&spec, &sources).map_err(|e| e.to_string())?; + #[cfg(feature = "eventlog")] + provider::check_model(&ir.to_canonical_json())?; let authored = manifest .scenarios .iter() - .map(|path| Ok(CoverageSource::new(path.clone(), read(&ess_root, path)?)?)) + .map(|path| Ok(CoverageSource::new(path.clone(), read(ess_root, path)?)?)) .collect::>>()?; let input = coverage_build::build(&ir, &authored, Scope::System, Origins::GeneratedAndAuthored)?; @@ -173,8 +179,8 @@ fn scenario_ids(suite: &AdmittedSuite) -> Vec { .collect() } -fn coverage(root: &Path, suite: &AdmittedSuite, review: Option<&str>) -> Result<()> { - let path = root.join("ess/coverage.json"); +fn coverage(ess_root: &Path, suite: &AdmittedSuite, review: Option<&str>) -> Result<()> { + let path = ess_root.join("coverage.json"); let scenarios = scenario_ids(suite); let contracts = suite .suite() @@ -261,32 +267,36 @@ fn run(root: &Path, suite: &AdmittedSuite, reports: &Path) -> Result<()> { fn main() -> Result<()> { let args = Args::parse(); let root = args.root.canonicalize()?; + let ess_root = root.join(args.spec_root).canonicalize()?; + if !ess_root.starts_with(&root) { + return Err("specification directory must be inside the repository".into()); + } match args.command { Command::Regenerate { coverage_review } => { - let (ir, suite) = generate(&root)?; - let second = generate(&root)?; + let (ir, suite) = generate(&ess_root)?; + let second = generate(&ess_root)?; if (ir.as_str(), suite.as_str()) != (second.0.as_str(), second.1.as_str()) { return Err("nondeterministic regeneration".into()); } coverage( - &root, + &ess_root, &AdmittedSuite::from_json(&suite)?, coverage_review.as_deref(), )?; - fs::create_dir_all(root.join("ess/generated"))?; - fs::write(root.join("ess/generated/model.json"), ir)?; - fs::write(root.join("ess/generated/suite.json"), suite)?; + fs::create_dir_all(ess_root.join("generated"))?; + fs::write(ess_root.join("generated/model.json"), ir)?; + fs::write(ess_root.join("generated/suite.json"), suite)?; } Command::Check { reports } => { - let (ir, suite) = generate(&root)?; - let second = generate(&root)?; + let (ir, suite) = generate(&ess_root)?; + let second = generate(&ess_root)?; if (ir.as_str(), suite.as_str()) != (second.0.as_str(), second.1.as_str()) { return Err("nondeterministic regeneration".into()); } - exact(&root.join("ess/generated/model.json"), &ir)?; - exact(&root.join("ess/generated/suite.json"), &suite)?; + exact(&ess_root.join("generated/model.json"), &ir)?; + exact(&ess_root.join("generated/suite.json"), &suite)?; let admitted = AdmittedSuite::from_json(&suite)?; - coverage(&root, &admitted, None)?; + coverage(&ess_root, &admitted, None)?; run(&root, &admitted, &reports)?; } Command::Run { @@ -294,9 +304,10 @@ fn main() -> Result<()> { scenario, reports, } => { - let text = fs::read_to_string( - root.join(suite.unwrap_or_else(|| "ess/generated/suite.json".into())), - )?; + let text = fs::read_to_string(match suite { + Some(path) => root.join(path), + None => ess_root.join("generated/suite.json"), + })?; let admitted = AdmittedSuite::from_json(&text)?; if let Some(id) = scenario { let selected = admitted diff --git a/checks/ess-conformance/src/provider.rs b/checks/ess-conformance/src/provider.rs new file mode 100644 index 00000000..263df090 --- /dev/null +++ b/checks/ess-conformance/src/provider.rs @@ -0,0 +1,314 @@ +//! Real SQLite provider and synchronous facade calls; SQL corruption is an explicit fixture. +//! These scenarios prove returned semantics and integrity. Bounded calls and retained-path cost +//! are verified separately by runtime counter tests and the measured workload. +use std::num::NonZeroU16; + +use entity_core::{EntityDefinition, Registry}; +use entity_eventlog::{ + sync::{ + BridgeConfig, BridgeStartError, CallWait, EventlogRecordedStoreOwner, + EventlogRecordedStoreProvisioner, ProvisionAuthority, ShutdownMode, SyncExecutionError, + SyncReadError, + }, + Authority, CapturePolicy, EventlogOperationContext, RecordedProviderFacade, +}; +use entity_store::asynchronous::{BatchKey, SubjectHistory}; +use ess_conformance::target::TargetError; +use eventlog_core::CaptureLimits; +use serde_json::{json, Value}; +use time::OffsetDateTime; + +use crate::{ + common::{self, Fields, TempRoot}, + executor, + store::{self, read, response, serialize, subject, Failure, Result}, +}; + +const PREFIX: &str = "ess_provider"; +const LIMITS: CaptureLimits = CaptureLimits { + max_events: 1024, + max_blobs: 8192, + max_projection_rows: 8192, + max_payload_bytes: 64 * 1024 * 1024, +}; + +/// The one hand-written transient enum must match the compiler's complete declaration. +pub fn check_model(model: &str) -> std::result::Result<(), Box> { + let model: Value = serde_json::from_str(model)?; + if model["domains"].get("entity-provider.tracking").is_some() { + let declaration = model["types"] + .get("entity-provider.tracking.CapturePolicy") + .ok_or("provider specification must declare CapturePolicy")?; + let variants = CapturePolicy::ALL.map(|policy| match policy { + CapturePolicy::FullVerification => "FullVerification", + CapturePolicy::ProviderTracked => "ProviderTracked", + }); + if declaration["body"] != json!({"kind":"enum","variants":variants}) { + return Err("CapturePolicy differs from the complete compiled ESS enum".into()); + } + } + Ok(()) +} + +pub struct Target { + facade: Option, + authority: Option, + registry: Registry, + root: TempRoot, +} + +impl Target { + pub fn new() -> std::result::Result { + Ok(Self { + facade: None, + authority: None, + registry: Registry::new(), + root: TempRoot::new()?, + }) + } + fn path(&self) -> String { + self.root + .0 + .join("provider.sqlite3") + .to_string_lossy() + .into_owned() + } + fn facade(&self) -> Result<&RecordedProviderFacade> { + self.facade + .as_ref() + .ok_or_else(|| Failure::named("NotOpen", "provider is not open")) + } + fn close(&mut self) { + if let Some(mut facade) = self.facade.take() { + let _ = facade.shutdown(ShutdownMode::Drain, CallWait::Forever); + } + } + fn reopen(&mut self, policy: CapturePolicy) -> Result { + let authority = self + .authority + .clone() + .ok_or_else(|| Failure::named("NotProvisioned", "no authority"))?; + self.close(); + self.facade = Some( + RecordedProviderFacade::start_with_read_policy( + self.registry.clone(), + EventlogRecordedStoreOwner::Sqlite { + path: self.path(), + prefix: PREFIX.into(), + authority, + limits: LIMITS, + }, + bridge(), + policy, + ) + .map_err(start_error)?, + ); + Ok(Value::Null) + } + pub fn execute( + &mut self, + operation: &str, + input: &Fields, + ) -> std::result::Result { + let request = common::text(input, "request")?; + let result = serde_json::from_str::(request) + .map_err(Failure::from) + .and_then(|value| self.call(operation, &value)); + response(result) + } + fn call(&mut self, operation: &str, value: &Value) -> Result { + match operation { + "Register" => { + if self.facade.is_some() { + return Err(Failure::named( + "AlreadyOpen", + "register definitions before opening", + )); + } + let definition: EntityDefinition = read(value, "definition")?; + self.registry.register(definition).map_err(Failure::debug)?; + Ok(Value::Null) + } + "Provision" => { + let policy = policy(value)?; + if self.authority.is_some() { + return Err(Failure::named( + "AlreadyProvisioned", + "fixture already has an authority", + )); + } + let facade = RecordedProviderFacade::provision( + self.registry.clone(), + EventlogRecordedStoreProvisioner::Sqlite { + path: self.path(), + prefix: PREFIX.into(), + authority: ProvisionAuthority { + logical_scope: "ess-scope".into(), + tenant: "ess-tenant".into(), + expected_stream_identity: None, + }, + limits: LIMITS, + }, + context("provision"), + bridge(), + ) + .map_err(start_error)?; + self.authority = Some(facade.authority().clone()); + self.facade = Some(facade); + self.reopen(policy) + } + "Reopen" => self.reopen(policy(value)?), + "Create" => { + let request = executor::create(value)?; + let outcome = self + .facade()? + .create( + context(&request.recording.record_id), + request, + CallWait::Forever, + ) + .map_err(execution_error)?; + Ok(store::outcome(outcome)) + } + "Batch" => { + let key: BatchKey = read(value, "key")?; + let actions = executor::actions(value)?; + let outcome = self + .facade()? + .execute_batch(context("batch"), key, actions, CallWait::Forever) + .map_err(execution_error)?; + Ok(store::outcome(outcome)) + } + "Load" => serialize( + self.facade()? + .scoped() + .load_recorded(&subject(value)?, CallWait::Forever) + .map_err(read_error)?, + ), + "Histories" => { + let subjects = read::>(value, "subjects")? + .iter() + .map(subject) + .collect::>>()?; + let histories = self + .facade()? + .scoped() + .read_histories(&subjects, CallWait::Forever) + .map_err(read_error)?; + Ok(Value::Array( + histories.iter().map(history_summary).collect(), + )) + } + "LookupBatch" => { + let batch = self + .facade()? + .scoped() + .lookup_batch(&read(value, "key")?, CallWait::Forever) + .map_err(read_error)?; + // Presence comes from the actual lookup; receipt coordinates remain covered by executor cases. + Ok(json!({"present":batch.is_some()})) + } + "Snapshot" => { + let snapshot = self + .facade()? + .complete_snapshot(CallWait::Forever) + .map_err(read_error)?; + Ok(Value::Array( + snapshot + .histories + .iter() + .map(|snapshot| history_summary(&snapshot.history)) + .collect(), + )) + } + "SqlMutate" => { + let kind: String = read(value, "kind")?; + let sql = match kind.as_str() { + "blob" => "UPDATE ess_provider_blobs SET bytes=zeroblob(length(bytes)) WHERE rowid=(SELECT MIN(rowid) FROM ess_provider_blobs)", + "event" => "UPDATE ess_provider_events SET data='{}' WHERE global_seq=(SELECT MAX(global_seq) FROM ess_provider_events)", + "projection" => "UPDATE ess_provider_p_er_subjects_v1 SET body='{}'", + "identity" => "UPDATE ess_provider_identity SET stream_identity='substituted-generation'", + "delete-blob" => "DELETE FROM ess_provider_blobs WHERE rowid=(SELECT MIN(rowid) FROM ess_provider_blobs)", + _ => return Err(Failure::named("Deserialize", "unknown SQL mutation fixture")), + }; + let connection = rusqlite::Connection::open(self.path()) + .map_err(|e| Failure::named("Fixture", e))?; + let before: i64 = connection + .query_row( + "SELECT MAX(global_seq) FROM ess_provider_events", + [], + |row| row.get(0), + ) + .map_err(|e| Failure::named("Fixture", e))?; + let changed = connection + .execute(sql, []) + .map_err(|e| Failure::named("Fixture", e))?; + let after: i64 = connection + .query_row( + "SELECT MAX(global_seq) FROM ess_provider_events", + [], + |row| row.get(0), + ) + .map_err(|e| Failure::named("Fixture", e))?; + Ok(json!({"changed":changed,"same_event_head":before==after})) + } + _ => Err(Failure::named("Unsupported", operation)), + } + } +} +impl Drop for Target { + fn drop(&mut self) { + self.close(); + } +} +fn history_summary(history: &SubjectHistory) -> Value { + let records: Vec<_> = history + .records + .iter() + .map(|record| { + json!({"record_id":record.entry.record_id(),"revision":record.entry.revision()}) + }) + .collect(); + json!({"entity":history.subject.entity,"id":history.subject.id,"records":records}) +} +fn policy(value: &Value) -> Result { + match read::(value, "policy")?.as_str() { + "FullVerification" => Ok(CapturePolicy::FullVerification), + "ProviderTracked" => Ok(CapturePolicy::ProviderTracked), + _ => Err(Failure::named("Deserialize", "unknown capture policy")), + } +} +fn bridge() -> BridgeConfig { + BridgeConfig { + queue_capacity: NonZeroU16::new(8).expect("nonzero constant"), + } +} +fn context(id: &str) -> EventlogOperationContext { + EventlogOperationContext { + subject: "ess-provider".into(), + actor: "ess-runner".into(), + request_id: id.into(), + trace_id: id.into(), + causation_id: None, + causation_depth: 0, + occurred_at: OffsetDateTime::UNIX_EPOCH, + } +} +fn read_error(error: SyncReadError) -> Failure { + match error { + SyncReadError::Store(error) => error.into(), + other => Failure::named("BridgeRead", format!("{other:?}")), + } +} +fn start_error(error: BridgeStartError) -> Failure { + match error { + BridgeStartError::Open(error) => error.into(), + other => Failure::named("BridgeStart", format!("{other:?}")), + } +} +fn execution_error(error: SyncExecutionError) -> Failure { + match error { + SyncExecutionError::Execution(error) => executor::execution_error(error), + other => Failure::named("BridgeExecution", format!("{other:?}")), + } +} diff --git a/checks/ess-conformance/src/target.rs b/checks/ess-conformance/src/target.rs index 44e7e051..5de1762f 100644 --- a/checks/ess-conformance/src/target.rs +++ b/checks/ess-conformance/src/target.rs @@ -10,6 +10,8 @@ struct Context { shell: shell::Target, store: store::Target, executor: executor::Target, + #[cfg(feature = "eventlog")] + provider: crate::provider::Target, } pub struct Target { @@ -46,6 +48,8 @@ impl ConformanceTarget for Target { shell: shell::Target::new()?, store: store::Target::new()?, executor: executor::Target::new()?, + #[cfg(feature = "eventlog")] + provider: crate::provider::Target::new()?, }); Ok(()) } @@ -71,6 +75,8 @@ impl ConformanceTarget for Target { "entity.query" => context.query.execute(operation, &request.input)?, "entity.store" => context.store.execute(operation, &request.input)?, "entity.executor" => context.executor.execute(operation, &request.input)?, + #[cfg(feature = "eventlog")] + "entity-provider.tracking" => context.provider.execute(operation, &request.input)?, _ => return Err(TargetError::unsupported(&name, "unknown library domain")), }; self.observations.borrow_mut().push( diff --git a/crates/entity-cli/Cargo.toml b/crates/entity-cli/Cargo.toml index ec89b18d..579ccd12 100644 --- a/crates/entity-cli/Cargo.toml +++ b/crates/entity-cli/Cargo.toml @@ -16,19 +16,19 @@ default = [] eventlog-providers = ["dep:entity-eventlog", "dep:entity-executor", "dep:eventlog-core", "dep:time"] [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } -entity-graph = { path = "../entity-graph", version = "0.25.1" } -entity-mcp = { path = "../entity-mcp", version = "0.25.1" } +entity-core = { path = "../entity-core", version = "0.26.0" } +entity-graph = { path = "../entity-graph", version = "0.26.0" } +entity-mcp = { path = "../entity-mcp", version = "0.26.0" } # The store-backed verbs share the exact-retry rule with MCP and the generated CLI, so they share # the one implementation of it rather than each keeping their own. -entity-shell = { path = "../entity-shell", version = "0.25.1" } -entity-surface = { path = "../entity-surface", version = "0.25.1" } +entity-shell = { path = "../entity-shell", version = "0.26.0" } +entity-surface = { path = "../entity-surface", version = "0.26.0" } # The shell's half of R-80: the CLI is a shell, so this is where a store belongs. -entity-store = { path = "../entity-store", version = "0.25.1" } -entity-yaml = { path = "../entity-yaml", version = "0.25.1" } -entity-eventlog = { path = "../entity-eventlog", version = "0.25.1", features = ["sync-bridge", "file"], optional = true } -entity-executor = { path = "../entity-executor", version = "0.25.1", optional = true } -eventlog-core = { git = "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/beyond10x/eventlog", rev = "0a0484634e8c640be29d6b6541d6cc1c1aaef7d3", optional = true } +entity-store = { path = "../entity-store", version = "0.26.0" } +entity-yaml = { path = "../entity-yaml", version = "0.26.0" } +entity-eventlog = { path = "../entity-eventlog", version = "0.26.0", features = ["sync-bridge", "file"], optional = true } +entity-executor = { path = "../entity-executor", version = "0.26.0", optional = true } +eventlog-core = { git = "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/beyond10x/eventlog", rev = "6983cc25eb92e07844b3a6fa3e0decbdb300f43c", optional = true } time = { version = "0.3", features = ["parsing"], optional = true } serde.workspace = true serde_json.workspace = true diff --git a/crates/entity-eventlog/Cargo.toml b/crates/entity-eventlog/Cargo.toml index eeec1bcb..5d0189bb 100644 --- a/crates/entity-eventlog/Cargo.toml +++ b/crates/entity-eventlog/Cargo.toml @@ -17,15 +17,15 @@ postgres = ["dep:eventlog-postgres"] tree = ["dep:eventlog-tree"] [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } -entity-store = { path = "../entity-store", version = "0.25.1" } -entity-executor = { path = "../entity-executor", version = "0.25.1", optional = true } -entity-query = { path = "../entity-query", version = "0.25.1", optional = true } -eventlog-core = { git = "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/beyond10x/eventlog", rev = "0a0484634e8c640be29d6b6541d6cc1c1aaef7d3" } -eventlog-file = { git = "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/beyond10x/eventlog", rev = "0a0484634e8c640be29d6b6541d6cc1c1aaef7d3", optional = true } -eventlog-sqlite = { git = "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/beyond10x/eventlog", rev = "0a0484634e8c640be29d6b6541d6cc1c1aaef7d3", optional = true } -eventlog-postgres = { git = "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/beyond10x/eventlog", rev = "0a0484634e8c640be29d6b6541d6cc1c1aaef7d3", optional = true } -eventlog-tree = { git = "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/beyond10x/eventlog", rev = "0a0484634e8c640be29d6b6541d6cc1c1aaef7d3", optional = true } +entity-core = { path = "../entity-core", version = "0.26.0" } +entity-store = { path = "../entity-store", version = "0.26.0" } +entity-executor = { path = "../entity-executor", version = "0.26.0", optional = true } +entity-query = { path = "../entity-query", version = "0.26.0", optional = true } +eventlog-core = { git = "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/beyond10x/eventlog", rev = "6983cc25eb92e07844b3a6fa3e0decbdb300f43c" } +eventlog-file = { git = "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/beyond10x/eventlog", rev = "6983cc25eb92e07844b3a6fa3e0decbdb300f43c", optional = true } +eventlog-sqlite = { git = "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/beyond10x/eventlog", rev = "6983cc25eb92e07844b3a6fa3e0decbdb300f43c", optional = true } +eventlog-postgres = { git = "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/beyond10x/eventlog", rev = "6983cc25eb92e07844b3a6fa3e0decbdb300f43c", optional = true } +eventlog-tree = { git = "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/beyond10x/eventlog", rev = "6983cc25eb92e07844b3a6fa3e0decbdb300f43c", optional = true } serde.workspace = true serde_json.workspace = true sha2 = "0.10" diff --git a/crates/entity-eventlog/src/adapter.rs b/crates/entity-eventlog/src/adapter.rs index 837018fa..541a15b7 100644 --- a/crates/entity-eventlog/src/adapter.rs +++ b/crates/entity-eventlog/src/adapter.rs @@ -49,6 +49,8 @@ use crate::{ mod memory; mod scoped; +mod tracked; +pub use tracked::CapturePolicy; #[cfg(all(test, feature = "sqlite", feature = "sync-bridge"))] mod small_store_cost; @@ -856,7 +858,7 @@ pub struct StoreCalls { pub captures: usize, /// Verified models built from a whole capture: every event, blob, history and row checked. pub model_builds: usize, - /// Verified models advanced by only the events this handle itself committed. + /// Verified models advanced by an attested append suffix instead of rebuilt from all events. pub model_advances: usize, /// Complete records decoded and re-encoded to their canonical bytes while verifying. pub records_decoded: usize, @@ -887,6 +889,8 @@ pub struct EventlogRecordedStore { /// Blobs and subject histories this handle has verified, under exactly what it verified, so /// that a later read of the same bytes is not hashed, decoded and replayed again. memory: Mutex, + policy: CapturePolicy, + tracked: Mutex, } /// What one write adds to the tenant: events, index rows, and the blobs it binds. @@ -944,6 +948,19 @@ impl EventlogRecordedStore { backend: Arc, authority: Authority, limits: CaptureLimits, + ) -> Result { + Self::open_with_policy(backend, authority, limits, CapturePolicy::FullVerification).await + } + + /// Opens with an explicit provider-continuity policy after complete initial verification. + /// + /// # Errors + /// The same authority and integrity refusals as [`Self::open`]. + pub async fn open_with_policy( + backend: Arc, + authority: Authority, + limits: CaptureLimits, + policy: CapturePolicy, ) -> Result { authority.validate()?; let tenant = TenantId::new(authority.tenant.clone()).map_err(input_eventlog)?; @@ -964,6 +981,8 @@ impl EventlogRecordedStore { held: Mutex::new(CaptureHeld::default()), own_events: Mutex::new(BTreeSet::new()), memory: Mutex::new(VerifiedMemory::default()), + policy, + tracked: Mutex::new(tracked::Cache::default()), }; let model = store.capture_model().await?; if model.binding.is_none() { @@ -1021,6 +1040,9 @@ impl EventlogRecordedStore { /// is the verified one plus events this handle's own appends returned is advanced by exactly /// those events; every other capture is built and verified whole, as before. async fn capture_model(&self) -> Result, AsyncStoreError> { + if self.policy == CapturePolicy::ProviderTracked { + return self.tracked_model().await; + } let capture = self.capture().await?; self.model_of(capture) } @@ -1189,6 +1211,9 @@ impl EventlogRecordedStore { /// Records the events one of this handle's own committed appends returned. fn remember_own(&self, result: &eventlog_core::AppendGroupResult) { + if self.policy == CapturePolicy::ProviderTracked { + return; + } if let Ok(mut own) = self.own_events.lock() { for append in &result.appends { for event in &append.events { @@ -1580,7 +1605,11 @@ impl EventlogOperationStore<'_> { if !heads.contains_key(&member.entry.subject()) && let Some(history) = model.histories.get(&member.entry.subject()) { - let tips = branch_tips(history); + let tips = if model.lineage_subjects.contains(&member.entry.subject()) { + branch_tips(history) + } else { + Vec::new() + }; if tips.len() > 1 { joins.insert(member.entry.subject(), tips); } @@ -3648,6 +3677,10 @@ struct CapturedModel { anchor_blob_digests: BTreeMap, /// Complete records this model's construction decoded, each a full canonical re-encoding. decoded: usize, + /// Subjects needing the complete lineage verifier rather than a linear suffix. + lineage_subjects: BTreeSet, + /// Last state-producing record, so observations do not require a backwards history scan. + state_records: BTreeMap, } struct PendingRecord { @@ -4257,6 +4290,21 @@ fn insert_committed( model .record_physical .insert(receipt.record_id.clone(), physical(&record.event)); + if saved.lineage.is_some() { + model.lineage_subjects.insert(subject.clone()); + } + if matches!( + saved.entry, + entity_store::asynchronous::RecordedEntry::Decision(_) + ) && model + .state_records + .get(&subject) + .is_none_or(|id| model.record_physical[id].global_seq < record.event.global_seq) + { + model + .state_records + .insert(subject.clone(), receipt.record_id.clone()); + } let history = model .histories diff --git a/crates/entity-eventlog/src/adapter/scoped.rs b/crates/entity-eventlog/src/adapter/scoped.rs index 289cd573..ff938d41 100644 --- a/crates/entity-eventlog/src/adapter/scoped.rs +++ b/crates/entity-eventlog/src/adapter/scoped.rs @@ -19,7 +19,7 @@ //! the rows the append guard later decides on under the provider's lock. use std::collections::{BTreeMap, BTreeSet}; -use std::sync::atomic::Ordering; +use std::sync::{Arc, atomic::Ordering}; use entity_store::asynchronous::{AppendRequest, AsyncStoreError, BatchKey, Subject}; use eventlog_core::{ @@ -118,7 +118,7 @@ pub(super) struct ScopedModel { /// batch reads, which share one read across a command, ask what a read covers. #[cfg(feature = "sync-bridge")] pub(super) covered: ReadScope, - pub(super) model: CapturedModel, + pub(super) model: Arc, } #[cfg(feature = "sync-bridge")] @@ -149,6 +149,13 @@ impl EventlogRecordedStore { &self, scope: &ReadScope, ) -> Result { + if self.policy == super::CapturePolicy::ProviderTracked { + return Ok(ScopedModel { + #[cfg(feature = "sync-bridge")] + covered: scope.clone(), + model: self.tracked_model().await?, + }); + } for _ in 0..ATTEMPTS { self.scoped_reads.fetch_add(1, Ordering::Relaxed); match self.scoped_once(scope).await { @@ -174,7 +181,7 @@ impl EventlogRecordedStore { .collect(), ..scope.clone() }, - model: (*complete).clone(), + model: complete, }) } @@ -335,7 +342,7 @@ impl EventlogRecordedStore { subjects: read, ..scope.clone() }, - model, + model: Arc::new(model), }) } diff --git a/crates/entity-eventlog/src/adapter/tracked.rs b/crates/entity-eventlog/src/adapter/tracked.rs new file mode 100644 index 00000000..71103ea5 --- /dev/null +++ b/crates/entity-eventlog/src/adapter/tracked.rs @@ -0,0 +1,815 @@ +//! Provider-attested reuse of a completely verified observation. +use super::*; +use eventlog_core::{CaptureCheckpoint, TenantCaptureDelta, TenantCaptureUpdate}; + +/// Integrity boundary for reads through one live provider handle. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum CapturePolicy { + /// Obtain fresh content for every complete read, including providers without change tracking. + #[default] + FullVerification, + /// Reuse only provider-attested unchanged content or an exactly verified append suffix. + /// + /// Initial open is always verified completely. SQLite tracks SQL writes through every + /// connection; raw file edits bypassing SQLite are outside this live-handle guarantee. + ProviderTracked, +} + +impl CapturePolicy { + /// The complete transient policy domain, in specification declaration order. + pub const ALL: [Self; 2] = [Self::FullVerification, Self::ProviderTracked]; +} + +#[derive(Default)] +pub(super) struct Cache { + generation: u64, + held: Option, +} + +struct Held { + checkpoint: Option, + model: Arc, + blobs: BTreeMap>, + rows: Vec>, + last_position: u64, +} + +impl EventlogRecordedStore { + pub(super) async fn tracked_model(&self) -> Result, AsyncStoreError> { + loop { + let (generation, checkpoint) = { + let cache = self + .tracked + .lock() + .map_err(|_| integrity("tracked cache poisoned"))?; + ( + cache.generation, + cache.held.as_ref().and_then(|h| h.checkpoint.clone()), + ) + }; + let update = self + .backend + .capture_tenant_since( + &self.tenant, + projection_specs(), + self.limits, + checkpoint.as_ref(), + ) + .await + .map_err(map_capture)?; + let answer = { + let mut cache = self + .tracked + .lock() + .map_err(|_| integrity("tracked cache poisoned"))?; + if cache.generation != generation { + continue; + } + let next_generation = generation + .checked_add(1) + .ok_or_else(|| integrity("tracked cache generation exhausted"))?; + let held = match update { + TenantCaptureUpdate::Complete { + capture, + checkpoint, + } => { + self.native_captures.fetch_add(1, Ordering::Relaxed); + if capture.tenant != self.tenant + || capture.stream_identity != self.authority.stream_identity + { + return Err(integrity("native capture substituted tenant generation")); + } + let model = build_model(&self.authority, &capture)?; + if model.binding.is_none() { + return Err(integrity("the tenant has no authoritative binding")); + } + self.model_builds.fetch_add(1, Ordering::Relaxed); + self.records_decoded + .fetch_add(model.decoded, Ordering::Relaxed); + *self + .held + .lock() + .map_err(|_| integrity("read bound lock poisoned"))? = + model.held.clone(); + Some(Held { + checkpoint, + model: Arc::new(model), + last_position: capture.events.last().map_or(0, |e| e.global_seq), + blobs: capture + .blobs + .into_iter() + .map(|b| (b.digest, b.bytes)) + .collect(), + rows: capture + .projections + .into_iter() + .map(|p| p.rows.into_iter().collect()) + .collect(), + }) + } + TenantCaptureUpdate::Unchanged { checkpoint } => { + let mut held = cache + .held + .take() + .ok_or_else(|| integrity("unchanged capture has no verified base"))?; + if held.checkpoint.is_none() { + return Err(integrity("unchanged capture has no provider checkpoint")); + } + held.checkpoint = Some(checkpoint); + Some(held) + } + TenantCaptureUpdate::AppendDelta { checkpoint, delta } => { + let added_digests: Vec<_> = + delta.blobs.iter().map(|blob| blob.digest.clone()).collect(); + let held = cache + .held + .take() + .ok_or_else(|| integrity("capture delta has no verified base"))?; + // A concurrent reader keeps its exact old model. Rebuild rather than + // copying the whole authority merely to advance one record. + advance(&self.authority, held, delta, checkpoint) + .ok() + .inspect(|held| { + if let Ok(mut bounds) = self.held.lock() { + bounds.digests.extend(added_digests); + } + self.model_advances.fetch_add(1, Ordering::Relaxed); + self.records_decoded + .fetch_add(held.model.decoded, Ordering::Relaxed); + }) + } + }; + cache.generation = next_generation; + cache.held = held; + if let Some(verified) = &cache.held { + // Publish accounting under the same generation lock as its model. A slower + // caller must not overwrite a newer observation's totals after releasing it. + let mut bounds = self + .held + .lock() + .map_err(|_| integrity("read bound lock poisoned"))?; + bounds.events = verified.model.held.events; + bounds.blobs = verified.model.held.blobs; + bounds.rows = verified.model.held.rows; + } + cache.held.as_ref().map(|h| Arc::clone(&h.model)) + }; + if let Some(model) = answer { + return Ok(model); + } + // Unsupported suffixes and refused tentative advances have no installed checkpoint. + // The next iteration asks for complete content and preserves the full verifier's error. + } + } + + #[cfg(feature = "sync-bridge")] + pub(crate) async fn scoped_histories( + &self, + subjects: &[Subject], + ) -> Result, AsyncStoreError> { + let mut scope = ReadScope::default(); + for subject in subjects { + subject.validate()?; + scope.subjects.insert(subject.clone()); + } + let read = self.scoped_model(&scope).await?; + Ok(subjects + .iter() + .map(|subject| model_history(&read.model, subject)) + .collect()) + } + + #[cfg(feature = "sync-bridge")] + pub(crate) async fn scoped_state( + &self, + subject: &Subject, + ) -> Result, AsyncStoreError> { + let read = self.scoped_model(&ReadScope::subject(subject)).await?; + model_state(&read.model, subject) + } + + #[cfg(feature = "sync-bridge")] + pub(crate) async fn scoped_record( + &self, + id: &str, + ) -> Result, AsyncStoreError> { + let read = self.scoped_model(&ReadScope::record(id)).await?; + Ok(read.model.records.get(id).cloned()) + } + + #[cfg(feature = "sync-bridge")] + pub(crate) async fn scoped_batch( + &self, + key: &BatchKey, + ) -> Result, AsyncStoreError> { + let read = self.scoped_model(&ReadScope::batch(key)).await?; + Ok(read.model.batches.get(key).cloned()) + } +} + +fn advance( + authority: &Authority, + held: Held, + delta: TenantCaptureDelta, + checkpoint: CaptureCheckpoint, +) -> Result { + if held.checkpoint.is_none() + || delta.tenant.as_str() != authority.tenant + || delta.stream_identity != authority.stream_identity + || delta + .events + .iter() + .any(|event| event.name != "er.recorded_entry" || event.digest.is_some()) + { + return Err(integrity("capture suffix requires complete verification")); + } + let Held { + model, + mut blobs, + mut rows, + mut last_position, + .. + } = held; + let mut model = Arc::try_unwrap(model) + .map_err(|_| integrity("verified model is held by another reader"))?; + let mut new_blobs = BTreeSet::new(); + for blob in delta.blobs { + if !new_blobs.insert(blob.digest.clone()) + || blobs.insert(blob.digest.clone(), blob.bytes).is_some() + { + return Err(integrity("capture suffix replaces a bound blob")); + } + model.held.digests.insert(blob.digest); + } + // Borrow only the suffix's references. Building a map of every retained blob is itself a + // whole-store scan, even when no bytes are decoded. + let mut wanted = BTreeSet::new(); + for event in &delta.events { + if event.global_seq <= last_position { + return Err(integrity("capture suffix does not advance tenant position")); + } + last_position = event.global_seq; + let digest = reference_digest(event)?; + let wrapper = decode_entry( + blobs + .get(digest) + .ok_or_else(|| integrity("suffix wrapper blob is missing"))?, + )?; + wanted.extend([ + digest.to_owned(), + wrapper.record_blob, + wrapper.request_blob, + wrapper.batch_blob, + ]); + } + let borrowed: BTreeMap<&str, &[u8]> = wanted + .iter() + .map(|digest| { + blobs + .get(digest) + .map(|bytes| (digest.as_str(), bytes.as_slice())) + .ok_or_else(|| integrity("capture suffix references a missing blob")) + }) + .collect::>()?; + let mut bound = BoundBlobs::new(&borrowed); + let mut pending = Vec::new(); + model.decoded = 0; + for event in &delta.events { + admit_event(authority, event, &mut bound, &mut model, &mut pending)?; + } + let mut starts = BTreeMap::new(); + let mut keys = BTreeSet::new(); + let mut ids = BTreeSet::new(); + for record in &pending { + let subject = record.entry.subject(); + if model.lineage_subjects.contains(&subject) + || model + .histories + .get(&subject) + .is_some_and(|h| !matches!(h.origin, HistoryOrigin::Genesis)) + { + return Err(integrity( + "nonlinear or imported suffix needs complete verification", + )); + } + starts + .entry(subject.clone()) + .or_insert_with(|| model.histories.get(&subject).map_or(0, |h| h.records.len())); + keys.insert(BatchKey::from(record.wrapper.batch_key.clone())); + ids.insert(record.entry.record_id().to_owned()); + } + insert_committed(pending, &mut bound, &mut model)?; + for (subject, start) in &starts { + let history = model + .histories + .get_mut(subject) + .ok_or_else(|| integrity("suffix subject missing"))?; + // Batches are admitted as whole groups; a coalesced delta can contain several keys in + // another lexical order. Sort only their new suffix, never the verified prefix. + history.records[*start..].sort_by_key(|record| record.position.store); + let mut previous = start.checked_sub(1).map(|i| history.records[i].position); + let mut state = model.terminals.get(subject).cloned(); + for record in &history.records[*start..] { + if previous.is_some_and(|p| { + p.store >= record.position.store || p.subject >= record.position.subject + }) { + return Err(corrupt( + subject, + "suffix positions do not follow verified history", + )); + } + // The remaining receipt fields are constructed from this exact entry and position + // by insert_committed. Its batch key still needs the full history verifier's checks. + record.receipt.batch_key.validate().map_err(|error| { + corrupt( + subject, + format!("record receipt carries an invalid key: {error}"), + ) + })?; + if let BatchKey::SingleRecord(id) = &record.receipt.batch_key + && (id != &record.receipt.record_id || record.receipt.member_index != 0) + { + return Err(corrupt( + subject, + "single-record receipt does not reproduce its record identity and zero index", + )); + } + state = validate_entry_against_state(&record.entry, record.expect, state.as_ref())?; + previous = Some(record.position); + } + model.terminals.insert( + subject.clone(), + state.ok_or_else(|| corrupt(subject, "suffix has no state-producing record"))?, + ); + } + // Reuse the existing exact row renderer against a compact model holding only changed + // coordinates. Old state-source metadata is included without cloning the old history. + let mut touched = CapturedModel::default(); + for id in &ids { + touched + .records + .insert(id.clone(), model.records[id].clone()); + touched + .record_physical + .insert(id.clone(), model.record_physical[id].clone()); + touched + .record_blob_digests + .insert(id.clone(), model.record_blob_digests[id].clone()); + } + for key in &keys { + touched + .batches + .insert(key.clone(), model.batches[key].clone()); + touched + .batch_blob_digests + .insert(key.clone(), model.batch_blob_digests[key].clone()); + } + for subject in starts.keys() { + let history = &model.histories[subject]; + let mut records = Vec::new(); + if let Some(id) = model.state_records.get(subject) { + let RecordLookup::Committed(record) = &model.records[id] else { + return Err(integrity("linear state source is not committed")); + }; + records.push(record.clone()); + touched + .record_blob_digests + .insert(id.clone(), model.record_blob_digests[id].clone()); + } + if let Some(last) = history.records.last() { + if records + .last() + .is_none_or(|record| record.entry.record_id() != last.entry.record_id()) + { + records.push(last.clone()); + } + touched.record_physical.insert( + last.entry.record_id().to_owned(), + model.record_physical[last.entry.record_id()].clone(), + ); + } + touched.histories.insert( + subject.clone(), + SubjectHistory { + subject: subject.clone(), + origin: HistoryOrigin::Genesis, + records, + }, + ); + touched + .terminals + .insert(subject.clone(), model.terminals[subject].clone()); + } + let expected = expected_projection_rows(authority, &touched)?; + let specs = projection_specs(); + let mut row_count = model.held.rows; + if delta.projections.len() != specs.len() || rows.len() != specs.len() { + return Err(integrity("capture suffix projection set differs")); + } + for (index, changes) in delta.projections.into_iter().enumerate() { + if changes.specification != specs[index] { + return Err(integrity("capture suffix projection specification differs")); + } + let mut seen = BTreeSet::new(); + for change in changes.rows { + if !seen.insert(change.key.clone()) + || rows[index].get(&change.key) != change.before.as_ref() + { + return Err(integrity("capture suffix projection before-value differs")); + } + let wanted = expected[index] + .get(&change.key) + .or_else(|| rows[index].get(&change.key)); + if wanted != change.after.as_ref() { + return Err(integrity( + "capture suffix projection differs from authoritative events", + )); + } + row_count = match (change.before.is_some(), change.after.is_some()) { + (false, true) => row_count.checked_add(1), + (true, false) => row_count.checked_sub(1), + _ => Some(row_count), + } + .ok_or_else(|| integrity("capture suffix row accounting overflow"))?; + match change.after { + Some(value) => { + rows[index].insert(change.key, value); + } + None => { + rows[index].remove(&change.key); + } + } + } + for (key, value) in &expected[index] { + if rows[index].get(key) != Some(value) { + return Err(integrity( + "capture suffix omitted an authoritative projection row", + )); + } + } + } + let usage = delta.resulting_usage; + if usage.events != model.held.events + delta.events.len() as u64 + || usage.blobs != model.held.blobs + new_blobs.len() as u64 + || usage.projection_rows != row_count + { + return Err(integrity( + "capture suffix usage differs from admitted additions", + )); + } + model.held.events = usage.events; + model.held.blobs = usage.blobs; + model.held.rows = usage.projection_rows; + Ok(Held { + checkpoint: Some(checkpoint), + model: Arc::new(model), + blobs, + rows, + last_position, + }) +} + +#[cfg(all(test, feature = "sqlite", feature = "sync-bridge"))] +mod review_tests; + +#[cfg(all(test, feature = "sqlite", feature = "sync-bridge"))] +mod tests { + use super::*; + use entity_executor::{CreateRequest, ExecuteRequest}; + use entity_store::Recording; + use eventlog_core::{CaptureUsage, CapturedProjectionDelta, CapturedRowChange}; + + const LIMITS: CaptureLimits = CaptureLimits { + max_events: 128, + max_blobs: 1024, + max_projection_rows: 1024, + max_payload_bytes: 16 * 1024 * 1024, + }; + fn context(label: &str) -> EventlogOperationContext { + EventlogOperationContext { + subject: "tracked-test".into(), + actor: "entity-eventlog-test".into(), + request_id: label.into(), + trace_id: label.into(), + causation_id: None, + causation_depth: 0, + occurred_at: OffsetDateTime::UNIX_EPOCH, + } + } + fn registry() -> Registry { + let mut registry = Registry::new(); + registry.register(serde_json::from_value(json!({"entity":"ticket","version":1,"schema":{"fields":{"title":{"type":"string","required":true}}},"lifecycle":{"initial":"Open","states":["Open"]},"operations":{"Touch":{"transitions":[{"from":"Open","to":"Open"}],"arguments":{"fields":{}},"emits":[]}}})).unwrap()).unwrap(); + registry + } + fn recording(id: &str) -> Recording { + Recording { + record_id: id.into(), + recorded_at: "2026-10-03T00:00:00Z".into(), + correlation: None, + causation: None, + actor: None, + } + } + async fn fixture( + policy: CapturePolicy, + ) -> ( + Arc, + EventlogRecordedStore, + Registry, + ) { + let backend = Arc::new( + eventlog_sqlite::SqliteEventStore::in_memory("tracked_test") + .await + .unwrap(), + ); + let tenant = TenantId::new("tracked-test").unwrap(); + let authority = Authority { + logical_scope: "tracked-scope".into(), + tenant: tenant.as_str().into(), + stream_identity: backend.stream_identity(&tenant).await.unwrap(), + }; + let projector = Arc::new(crate::ErRecordedProjector::new()); + backend.create_projections(projector.clone()).await.unwrap(); + backend.attach_inline_existing(projector).await.unwrap(); + EventlogBindingProvisioner::new(backend.clone(), LIMITS) + .provision_binding(authority.clone(), context("binding")) + .await + .unwrap(); + let store = + EventlogRecordedStore::open_with_policy(backend.clone(), authority, LIMITS, policy) + .await + .unwrap(); + (backend, store, registry()) + } + async fn append(store: &EventlogRecordedStore, registry: &Registry, revision: u64) { + let id = format!("record-{revision}"); + let subject = Subject::new("ticket", "clock").unwrap(); + let action = if revision == 0 { + BatchAction::Create(CreateRequest { + subject, + definition_version: 1, + fields: json!({"title":"clock"}), + recording: recording(&id), + }) + } else { + BatchAction::Execute(ExecuteRequest { + subject, + expected_revision: revision, + operation: "Touch".into(), + arguments: json!({}), + fulfillments: BTreeMap::new(), + recording: recording(&id), + }) + }; + store + .operation(context(&id)) + .execute_batch(registry, BatchKey::Named(id), vec![action]) + .await + .unwrap(); + } + fn held(authority: &Authority, capture: TenantCapture) -> Held { + Held { + checkpoint: Some(CaptureCheckpoint::new(())), + model: Arc::new(build_model(authority, &capture).unwrap()), + last_position: capture.events.last().map_or(0, |e| e.global_seq), + blobs: capture + .blobs + .into_iter() + .map(|b| (b.digest, b.bytes)) + .collect(), + rows: capture + .projections + .into_iter() + .map(|p| p.rows.into_iter().collect()) + .collect(), + } + } + fn difference(before: &TenantCapture, after: &TenantCapture) -> TenantCaptureDelta { + let old: BTreeSet<_> = before.blobs.iter().map(|b| b.digest.as_str()).collect(); + TenantCaptureDelta { + tenant: after.tenant.clone(), + stream_identity: after.stream_identity.clone(), + events: after.events[before.events.len()..].to_vec(), + blobs: after + .blobs + .iter() + .filter(|b| !old.contains(b.digest.as_str())) + .cloned() + .collect(), + projections: before + .projections + .iter() + .zip(&after.projections) + .map(|(before, after)| { + let old: BTreeMap<_, _> = before.rows.iter().cloned().collect(); + let new: BTreeMap<_, _> = after.rows.iter().cloned().collect(); + let keys: BTreeSet<_> = old.keys().chain(new.keys()).cloned().collect(); + CapturedProjectionDelta { + specification: after.specification, + rows: keys + .into_iter() + .filter(|k| old.get(k) != new.get(k)) + .map(|key| CapturedRowChange { + before: old.get(&key).cloned(), + after: new.get(&key).cloned(), + key, + }) + .collect(), + } + }) + .collect(), + resulting_usage: CaptureUsage { + events: after.events.len() as u64, + blobs: after.blobs.len() as u64, + projection_rows: projection_rows(after), + payload_bytes: 0, + }, + } + } + #[test] + fn tracked_unchanged_reads_and_appends_do_not_recapture_verified_prefixes() { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("test runtime") + .block_on(async { + let (backend, store, registry) = fixture(CapturePolicy::ProviderTracked).await; + let before = store.calls(); + let subject = Subject::new("ticket", "clock").unwrap(); + assert_eq!(store.load(&subject).await.unwrap(), None); + assert_eq!( + store.calls().captures, + before.captures, + "unchanged provider proof must avoid another capture" + ); + for revision in 0..4 { + append(&store, ®istry, revision).await; + let complete = backend + .capture_tenant(&store.tenant, projection_specs(), LIMITS) + .await + .unwrap(); + let full = build_model(&store.authority, &complete).unwrap(); + assert_eq!( + store.load(&subject).await.unwrap(), + full.terminals.get(&subject).cloned() + ); + assert_eq!( + store.history(&subject).await.unwrap(), + full.histories[&subject] + ); + } + assert_eq!( + store.calls().captures, + before.captures, + "acknowledged linear suffixes must not reread old content" + ); + assert_eq!(store.calls().model_advances - before.model_advances, 4); + }); + } + #[test] + fn delta_verification_matches_full_models_and_refuses_missing_or_forged_coordinates() { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("test runtime") + .block_on(async { + let (backend, store, registry) = fixture(CapturePolicy::FullVerification).await; + append(&store, ®istry, 0).await; + let before = backend + .capture_tenant(&store.tenant, projection_specs(), LIMITS) + .await + .unwrap(); + append(&store, ®istry, 1).await; + append(&store, ®istry, 2).await; + let after = backend + .capture_tenant(&store.tenant, projection_specs(), LIMITS) + .await + .unwrap(); + let full = build_model(&store.authority, &after).unwrap(); + let advanced = advance( + &store.authority, + held(&store.authority, before.clone()), + difference(&before, &after), + CaptureCheckpoint::new(()), + ) + .unwrap(); + assert_eq!(advanced.model.histories, full.histories); + assert_eq!(advanced.model.terminals, full.terminals); + assert_eq!(advanced.model.records, full.records); + assert_eq!(advanced.model.batches, full.batches); + for mutation in 0..6 { + let mut delta = difference(&before, &after); + match mutation { + 0 => { + delta.projections[1].rows.pop(); + } + 1 => { + delta.projections[2].rows.pop(); + } + 2 => { + delta.projections[3].rows.pop(); + } + 3 => { + delta.projections[3].rows[0].before = None; + } + 4 => { + delta.events[0].global_seq = before.events.last().unwrap().global_seq; + } + _ => { + delta.projections[3].rows[0].after = Some(json!(["forged"])); + } + } + let result = advance( + &store.authority, + held(&store.authority, before.clone()), + delta, + CaptureCheckpoint::new(()), + ); + assert!( + matches!( + result, + Err(AsyncStoreError::ProviderIntegrity { .. }) + | Err(AsyncStoreError::CorruptHistory { .. }) + ), + "mutation {mutation} must be refused" + ); + } + }); + } + + #[test] + fn held_reader_keeps_its_old_model_while_a_refresh_rebuilds_safely() { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("test runtime") + .block_on(async { + let (_, store, registry) = fixture(CapturePolicy::ProviderTracked).await; + append(&store, ®istry, 0).await; + let subject = Subject::new("ticket", "clock").unwrap(); + let pinned = store.tracked_model().await.unwrap(); + let before = store.calls(); + append(&store, ®istry, 1).await; + assert_eq!(pinned.terminals[&subject].revision, 1); + assert_eq!(store.load(&subject).await.unwrap().unwrap().revision, 2); + assert_eq!(store.calls().captures, before.captures + 1); + drop(pinned); + let before = store.calls(); + append(&store, ®istry, 2).await; + assert_eq!(store.calls().captures, before.captures); + assert_eq!(store.calls().model_advances, before.model_advances + 1); + }); + } + + #[test] + fn unjournaled_provider_mutation_invalidates_the_model_before_reuse() { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("test runtime") + .block_on(async { + let (backend, store, registry) = fixture(CapturePolicy::ProviderTracked).await; + append(&store, ®istry, 0).await; + let subject = Subject::new("ticket", "clock").unwrap(); + let before = store.calls(); + let bytes = b"unreferenced-content"; + let digest = framed_key("er.test-orphan/1", bytes).unwrap(); + backend + .put_blob(&store.tenant, &digest, bytes) + .await + .unwrap(); + assert_eq!(store.load(&subject).await.unwrap().unwrap().revision, 1); + assert_eq!(store.calls().captures, before.captures + 1); + }); + } + + #[test] + fn one_scoped_history_read_preserves_order_duplicates_and_absence() { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("test runtime") + .block_on(async { + let (_, store, registry) = fixture(CapturePolicy::ProviderTracked).await; + append(&store, ®istry, 0).await; + let subject = Subject::new("ticket", "clock").unwrap(); + let absent = Subject::new("ticket", "absent").unwrap(); + let before = store.calls(); + let histories = store + .scoped_histories(&[subject.clone(), absent.clone(), subject.clone()]) + .await + .unwrap(); + assert_eq!(histories[0], histories[2]); + assert_eq!(histories[0].records.len(), 1); + assert_eq!( + histories[1], + SubjectHistory { + subject: absent, + origin: HistoryOrigin::Genesis, + records: Vec::new() + } + ); + assert_eq!(store.calls().captures, before.captures); + }); + } +} diff --git a/crates/entity-eventlog/src/adapter/tracked/review_tests.rs b/crates/entity-eventlog/src/adapter/tracked/review_tests.rs new file mode 100644 index 00000000..914face4 --- /dev/null +++ b/crates/entity-eventlog/src/adapter/tracked/review_tests.rs @@ -0,0 +1,419 @@ +//! Independent full-versus-incremental integrity probes. +use super::*; +use entity_executor::{CreateRequest, ExecuteRequest}; +use entity_store::asynchronous::RecordedEntry; +use entity_store::{RecordedObservation, Recording}; +use eventlog_core::{CaptureUsage, CapturedBlob, CapturedProjectionDelta, CapturedRowChange}; + +const LIMITS: CaptureLimits = CaptureLimits { + max_events: 128, + max_blobs: 1024, + max_projection_rows: 1024, + max_payload_bytes: 16 * 1024 * 1024, +}; +fn context(id: &str) -> EventlogOperationContext { + EventlogOperationContext { + subject: "review".into(), + actor: "entity-eventlog-test".into(), + request_id: id.into(), + trace_id: id.into(), + causation_id: None, + causation_depth: 0, + occurred_at: OffsetDateTime::UNIX_EPOCH, + } +} +fn recording(id: &str) -> Recording { + Recording { + record_id: id.into(), + recorded_at: "2026-10-03T00:00:00Z".into(), + correlation: None, + causation: None, + actor: None, + } +} +fn touch(id: &str, revision: u64) -> BatchAction { + BatchAction::Execute(ExecuteRequest { + subject: Subject::new("ticket", "clock").unwrap(), + expected_revision: revision, + operation: "Touch".into(), + arguments: json!({}), + fulfillments: BTreeMap::new(), + recording: recording(id), + }) +} +fn observe(id: &str, revision: u64) -> BatchAction { + BatchAction::Observe(RecordedObservation { + entity: "ticket".into(), + id: "clock".into(), + revision, + envelope: recording(id).seal(json!({"observed":true})).unwrap(), + }) +} +async fn fixture() -> ( + Arc, + EventlogRecordedStore, + Registry, +) { + let backend = Arc::new( + eventlog_sqlite::SqliteEventStore::in_memory("runtime_review") + .await + .unwrap(), + ); + let tenant = TenantId::new("runtime-review").unwrap(); + let authority = Authority { + logical_scope: "review-scope".into(), + tenant: tenant.as_str().into(), + stream_identity: backend.stream_identity(&tenant).await.unwrap(), + }; + let projector = Arc::new(crate::ErRecordedProjector::new()); + backend.create_projections(projector.clone()).await.unwrap(); + backend.attach_inline_existing(projector).await.unwrap(); + EventlogBindingProvisioner::new(backend.clone(), LIMITS) + .provision_binding(authority.clone(), context("binding")) + .await + .unwrap(); + let store = EventlogRecordedStore::open_with_policy( + backend.clone(), + authority, + LIMITS, + CapturePolicy::ProviderTracked, + ) + .await + .unwrap(); + let mut registry = Registry::new(); + registry + .register( + serde_json::from_value(json!({"entity":"ticket","version":1, + "schema":{"fields":{}},"lifecycle":{"initial":"Open","states":["Open"]}, + "operations":{"Touch":{"transitions":[{"from":"Open","to":"Open"}],"emits":[]}} + })) + .unwrap(), + ) + .unwrap(); + store + .operation(context("create")) + .execute_batch( + ®istry, + BatchKey::Named("create".into()), + vec![BatchAction::Create(CreateRequest { + subject: Subject::new("ticket", "clock").unwrap(), + definition_version: 1, + fields: json!({}), + recording: recording("created"), + })], + ) + .await + .unwrap(); + (backend, store, registry) +} +async fn capture( + backend: &eventlog_sqlite::SqliteEventStore, + store: &EventlogRecordedStore, +) -> TenantCapture { + backend + .capture_tenant(&store.tenant, projection_specs(), LIMITS) + .await + .unwrap() +} +fn retained(authority: &Authority, capture: TenantCapture) -> Held { + Held { + checkpoint: Some(CaptureCheckpoint::new(())), + model: Arc::new(build_model(authority, &capture).unwrap()), + last_position: capture.events.last().unwrap().global_seq, + blobs: capture + .blobs + .into_iter() + .map(|b| (b.digest, b.bytes)) + .collect(), + rows: capture + .projections + .into_iter() + .map(|p| p.rows.into_iter().collect()) + .collect(), + } +} +fn delta(before: &TenantCapture, after: &TenantCapture) -> TenantCaptureDelta { + let old: BTreeSet<_> = before.blobs.iter().map(|b| b.digest.as_str()).collect(); + TenantCaptureDelta { + tenant: after.tenant.clone(), + stream_identity: after.stream_identity.clone(), + events: after.events[before.events.len()..].to_vec(), + blobs: after + .blobs + .iter() + .filter(|b| !old.contains(b.digest.as_str())) + .cloned() + .collect(), + projections: before + .projections + .iter() + .zip(&after.projections) + .map(|(before, after)| { + let old: BTreeMap<_, _> = before.rows.iter().cloned().collect(); + let new: BTreeMap<_, _> = after.rows.iter().cloned().collect(); + let keys: BTreeSet<_> = old.keys().chain(new.keys()).cloned().collect(); + CapturedProjectionDelta { + specification: after.specification, + rows: keys + .into_iter() + .filter(|k| old.get(k) != new.get(k)) + .map(|key| CapturedRowChange { + before: old.get(&key).cloned(), + after: new.get(&key).cloned(), + key, + }) + .collect(), + } + }) + .collect(), + resulting_usage: CaptureUsage { + events: after.events.len() as u64, + blobs: after.blobs.len() as u64, + projection_rows: projection_rows(after), + payload_bytes: 0, + }, + } +} + +#[tokio::test] +async fn incremental_receipts_refuse_a_single_record_key_that_names_another_record() { + let (backend, store, registry) = fixture().await; + let before = capture(&backend, &store).await; + store + .operation(context("touch")) + .execute_batch( + ®istry, + BatchKey::Named("touch".into()), + vec![touch("touched", 1)], + ) + .await + .unwrap(); + let mut after = capture(&backend, &store).await; + let original = build_model(&store.authority, &after).unwrap(); + let RecordLookup::Committed(record) = original.records["touched"].clone() else { + panic!("commit") + }; + let key = BatchKey::SingleRecord("another-record".into()); + let batch_bytes = batch_comparison_bytes( + &key, + &[AppendMember::new( + record.expect, + record.entry.clone(), + record.request_bytes.clone(), + )], + ) + .unwrap(); + let batch_digest = framed_key(BATCH_BLOB_DOMAIN, &batch_bytes).unwrap(); + let event = after.events.last_mut().unwrap(); + let old_digest = reference_digest(event).unwrap(); + let mut wrapper = decode_entry( + &after + .blobs + .iter() + .find(|b| b.digest == old_digest) + .unwrap() + .bytes, + ) + .unwrap(); + wrapper.batch_key = crate::encoding::BatchKeyWire::from(&key); + wrapper.batch_blob = batch_digest.clone(); + let wrapper_bytes = encode_entry(&wrapper).unwrap(); + let wrapper_digest = framed_key(ENTRY_BLOB_DOMAIN, &wrapper_bytes).unwrap(); + event.data = json!({"blob":wrapper_digest}); + after.blobs.extend([ + CapturedBlob { + digest: batch_digest, + bytes: batch_bytes, + }, + CapturedBlob { + digest: wrapper_digest, + bytes: wrapper_bytes, + }, + ]); + after.blobs.sort_by(|a, b| a.digest.cmp(&b.digest)); + // Materialize the malformed storage facts without running the full history verifier. + // This is a deliberately constructed provider observation, not an honest ER writer output. + let mut unverified = build_model(&store.authority, &before).unwrap(); + let borrowed: BTreeMap<_, _> = after + .blobs + .iter() + .map(|b| (b.digest.as_str(), b.bytes.as_slice())) + .collect(); + let mut bound = BoundBlobs::new(&borrowed); + let mut pending = Vec::new(); + admit_event( + &store.authority, + after.events.last().unwrap(), + &mut bound, + &mut unverified, + &mut pending, + ) + .unwrap(); + insert_committed(pending, &mut bound, &mut unverified).unwrap(); + unverified.terminals = original.terminals.clone(); + let rows = expected_projection_rows(&store.authority, &unverified).unwrap(); + for (projection, rows) in after.projections.iter_mut().zip(rows) { + projection.rows = rows.into_iter().collect(); + } + let full = build_model(&store.authority, &after); + assert!( + matches!(full,Err(AsyncStoreError::CorruptHistory {ref detail,..}) + if detail.contains("single-record receipt")), + "full verifier must reject receipt identity" + ); + let result = advance( + &store.authority, + retained(&store.authority, before.clone()), + delta(&before, &after), + CaptureCheckpoint::new(()), + ); + assert!( + matches!( + result, + Err(AsyncStoreError::CorruptHistory { .. }) + | Err(AsyncStoreError::ProviderIntegrity { .. }) + ), + "incremental verifier accepted a single-record receipt that the full verifier refuses" + ); +} + +#[tokio::test] +async fn coalesced_reverse_named_batches_keep_observation_heads_and_latest_state_source() { + let (backend, store, registry) = fixture().await; + let before = capture(&backend, &store).await; + store + .operation(context("z")) + .execute_batch( + ®istry, + BatchKey::Named("z-last-lexically".into()), + vec![touch("touch-z", 1), observe("observe-z", 2)], + ) + .await + .unwrap(); + store + .operation(context("a")) + .execute_batch( + ®istry, + BatchKey::Named("a-first-lexically".into()), + vec![ + observe("observe-a-before", 2), + touch("touch-a", 2), + observe("observe-a-after", 3), + ], + ) + .await + .unwrap(); + let after = capture(&backend, &store).await; + let full = build_model(&store.authority, &after).unwrap(); + let advanced = advance( + &store.authority, + retained(&store.authority, before.clone()), + delta(&before, &after), + CaptureCheckpoint::new(()), + ) + .unwrap(); + let subject = Subject::new("ticket", "clock").unwrap(); + assert_eq!(advanced.model.terminals[&subject].revision, 3); + assert_eq!(advanced.model.state_records[&subject], "touch-a"); + assert_eq!(advanced.model.histories, full.histories); + assert_eq!(advanced.model.records, full.records); + assert_eq!(advanced.model.batches, full.batches); + assert_eq!( + advanced.rows, + after + .projections + .iter() + .map(|p| p.rows.iter().cloned().collect()) + .collect::>>() + ); + let mut missing = delta(&before, &after); + missing.events.remove(1); + assert!( + matches!( + advance( + &store.authority, + retained(&store.authority, before), + missing, + CaptureCheckpoint::new(()) + ), + Err(AsyncStoreError::ProviderIntegrity { .. }) + | Err(AsyncStoreError::CorruptHistory { .. }) + ), + "incomplete newly acknowledged batch must not advance verified state" + ); +} + +#[tokio::test] +async fn acknowledged_native_append_with_blank_named_key_is_refused_before_tracked_reuse() { + let (backend, store, registry) = fixture().await; + let subject = Subject::new("ticket", "clock").unwrap(); + let predecessor = store.load(&subject).await.unwrap().unwrap(); + let decision = entity_core::Runtime::new(®istry) + .execute(&predecessor, "Touch", json!({})) + .unwrap(); + let entry = RecordedEntry::Decision( + entity_store::RecordedCommit::new(decision, &recording("bad-key-record")).unwrap(), + ); + let request_bytes = original_request_comparison_bytes(&entry).unwrap(); + let member = AppendMember::new(Expect::Revision(1), entry.clone(), request_bytes.clone()); + let mut batch: Value = serde_json::from_slice( + &batch_comparison_bytes(&BatchKey::Named("placeholder".into()), &[member]).unwrap(), + ) + .unwrap(); + batch[1] = json!(["named", ""]); + let batch_bytes = serde_json::to_vec(&batch).unwrap(); + let batch_digest = framed_key(BATCH_BLOB_DOMAIN, &batch_bytes).unwrap(); + let record_bytes = record_comparison_bytes(&entry).unwrap(); + let record_digest = framed_key(RECORD_BLOB_DOMAIN, &record_bytes).unwrap(); + let request_digest = framed_key(REQUEST_BLOB_DOMAIN, &request_bytes).unwrap(); + let wrapper = RecordedEntryWrapper { + authority: store.authority.clone(), + batch_blob: batch_digest.clone(), + batch_key: crate::encoding::BatchKeyWire::from(&BatchKey::Named(String::new())), + member_index: 0, + record_blob: record_digest.clone(), + request_blob: request_digest.clone(), + subject: SubjectWire::from(&subject), + }; + let wrapper_bytes = encode_entry(&wrapper).unwrap(); + let wrapper_digest = framed_key(ENTRY_BLOB_DOMAIN, &wrapper_bytes).unwrap(); + let group = AppendGroup { + tenant: store.tenant.clone(), + meta: context("bad-key-provider") + .meta("bad-key-provider-command".into(), batch_digest.clone()) + .unwrap(), + appends: vec![StreamAppend { + stream: StreamId::new( + store.tenant.clone(), + "er.subject", + subject_stream_id(&store.authority, &subject).unwrap(), + ) + .unwrap(), + expected: Expected::Exact(1), + events: vec![ + NewEvent::new("er.recorded_entry", 1, json!({"blob":wrapper_digest})).unwrap(), + ], + }], + }; + let blobs = vec![ + (batch_digest, batch_bytes), + (record_digest, record_bytes), + (request_digest, request_bytes), + (wrapper_digest, wrapper_bytes), + ]; + let committed = backend + .append_group_guarded_with_blobs(&group, Arc::new(eventlog_core::NoGuard), &blobs) + .await + .expect("real SQLite and default ER projector admit this native group"); + assert!(!committed.deduplicated); + let full = build_model(&store.authority, &capture(&backend, &store).await); + assert!( + matches!(full,Err(AsyncStoreError::CorruptHistory {ref detail,..}) if detail.contains("invalid key")), + "full verifier must reject invalid receipt batch key" + ); + let result = store.lookup_record("bad-key-record").await; + assert!( + matches!(result,Err(AsyncStoreError::CorruptHistory {ref detail,..}) if detail.contains("invalid key")), + "tracked read accepted malformed committed receipt: {result:?}" + ); +} diff --git a/crates/entity-eventlog/src/facade.rs b/crates/entity-eventlog/src/facade.rs index 726fb080..3ec6badb 100644 --- a/crates/entity-eventlog/src/facade.rs +++ b/crates/entity-eventlog/src/facade.rs @@ -158,12 +158,39 @@ impl RecordedProviderFacade { registry: Registry, owner: EventlogRecordedStoreOwner, config: BridgeConfig, + ) -> Result { + Self::start_with_read_policy( + registry, + owner, + config, + crate::CapturePolicy::FullVerification, + ) + } + + /// Opens with an explicit capture policy after complete initial authority verification. + /// + /// # Errors + /// The same provider-open refusals as [`Self::start`]. + pub fn start_with_read_policy( + registry: Registry, + owner: EventlogRecordedStoreOwner, + config: BridgeConfig, + policy: crate::CapturePolicy, ) -> Result { let authority = owner.authority().clone(); - let bridge = RecordedEventlogBridge::start(registry, owner, config)?; + let bridge = + RecordedEventlogBridge::start_with_read_policy(registry, owner, config, policy)?; Ok(Self { authority, bridge }) } + /// Reads only an explicitly named scope, with whole-batch evidence for that scope. + #[must_use] + pub fn scoped(&self) -> ScopedRecordedProviderFacade<'_> { + ScopedRecordedProviderFacade { + bridge: &self.bridge, + } + } + /// Explicitly provisions the fixed projections and immutable binding before opening. /// /// # Errors @@ -638,3 +665,95 @@ fn sync_read_error(error: SyncReadError) -> StoreError { }, } } + +/// Explicitly scoped reads through a live recorded provider facade. +/// +/// Full verification uses the ordinary scoped verifier. Provider tracking reuses a complete +/// verified model only under provider authority; unsupported tracking takes a fresh complete +/// capture. The view owns no independent worker and retains its parent's capture policy. +pub struct ScopedRecordedProviderFacade<'a> { + bridge: &'a RecordedEventlogBridge, +} + +impl ScopedRecordedProviderFacade<'_> { + /// Loads one subject's verified state. + pub fn load_recorded( + &self, + subject: &Subject, + wait: CallWait, + ) -> Result, SyncReadError> { + let crate::sync::ScopedAnswer::State(state) = self.bridge.scoped_read( + crate::sync::ScopedRead::Load(subject.clone()), + crate::sync::BridgeReadKind::Load, + wait, + )? + else { + unreachable!("typed scoped state request") + }; + Ok(state) + } + + /// Reads one subject's complete mixed history within its verified scope. + pub fn read_history( + &self, + subject: &Subject, + wait: CallWait, + ) -> Result { + Ok(self + .read_histories(std::slice::from_ref(subject), wait)? + .remove(0)) + } + + /// Reads all named histories with one worker request and one verified scope. + /// + /// Output preserves input order and duplicates; absent subjects have empty genesis histories. + pub fn read_histories( + &self, + subjects: &[Subject], + wait: CallWait, + ) -> Result, SyncReadError> { + let crate::sync::ScopedAnswer::Histories(histories) = self.bridge.scoped_read( + crate::sync::ScopedRead::Histories(subjects.to_vec()), + crate::sync::BridgeReadKind::History, + wait, + )? + else { + unreachable!("typed scoped histories request") + }; + Ok(histories) + } + + /// Looks up one original global record identity through scoped verification. + pub fn lookup_record( + &self, + id: &str, + wait: CallWait, + ) -> Result, SyncReadError> { + let crate::sync::ScopedAnswer::Record(record) = self.bridge.scoped_read( + crate::sync::ScopedRead::Record(id.into()), + crate::sync::BridgeReadKind::LookupRecord, + wait, + )? + else { + unreachable!("typed scoped record request") + }; + Ok(*record) + } + + /// Looks up one batch, verifying every member of the claimed batch. + pub fn lookup_batch( + &self, + key: &BatchKey, + wait: CallWait, + ) -> Result, SyncReadError> { + let crate::sync::ScopedAnswer::Batch(batch) = self.bridge.scoped_read( + crate::sync::ScopedRead::Batch(key.clone()), + crate::sync::BridgeReadKind::LookupBatch, + wait, + )? + else { + unreachable!("typed scoped batch request") + }; + Ok(batch) + } +} diff --git a/crates/entity-eventlog/src/lib.rs b/crates/entity-eventlog/src/lib.rs index a0ba4c52..937a2e24 100644 --- a/crates/entity-eventlog/src/lib.rs +++ b/crates/entity-eventlog/src/lib.rs @@ -13,7 +13,7 @@ mod projection; pub mod sync; pub use adapter::{ - AsyncBindingProvisioner, AsyncImportedAnchorWriter, EventlogBackend, + AsyncBindingProvisioner, AsyncImportedAnchorWriter, CapturePolicy, EventlogBackend, EventlogBindingProvisioner, EventlogOperationContext, EventlogOperationStore, EventlogRecordedStore, ImportAnchorFailure, ImportAnchorOutcome, ImportAnchorUncertainty, ProvisionBindingFailure, ProvisionBindingOutcome, ProvisionBindingUncertainty, StoreCalls, @@ -22,5 +22,7 @@ pub use encoding::{Authority, PhysicalRef}; #[cfg(all(feature = "sync-bridge", feature = "file"))] pub use facade::EventlogFileStore; #[cfg(feature = "sync-bridge")] -pub use facade::{LegacyImportError, LegacyImportReport, RecordedProviderFacade}; +pub use facade::{ + LegacyImportError, LegacyImportReport, RecordedProviderFacade, ScopedRecordedProviderFacade, +}; pub use projection::{ErRecordedProjector, projection_specs}; diff --git a/crates/entity-eventlog/src/sync.rs b/crates/entity-eventlog/src/sync.rs index 90730d73..1815b29f 100644 --- a/crates/entity-eventlog/src/sync.rs +++ b/crates/entity-eventlog/src/sync.rs @@ -251,7 +251,10 @@ impl EventlogRecordedStoreOwner { } } - async fn open(self) -> Result<(EventlogRecordedStore, OwnedBackend), AsyncStoreError> { + async fn open_with_policy( + self, + policy: crate::CapturePolicy, + ) -> Result<(EventlogRecordedStore, OwnedBackend), AsyncStoreError> { match self { #[cfg(feature = "tree")] Self::Tree { @@ -278,7 +281,8 @@ impl EventlogRecordedStoreOwner { ); let backend: Arc = concrete.clone(); Ok(( - EventlogRecordedStore::open(backend, authority, limits).await?, + EventlogRecordedStore::open_with_policy(backend, authority, limits, policy) + .await?, OwnedBackend::Tree(concrete), )) } @@ -299,7 +303,8 @@ impl EventlogRecordedStoreOwner { .map_err(store_open)?; let backend: Arc = concrete.clone(); Ok(( - EventlogRecordedStore::open(backend, authority, limits).await?, + EventlogRecordedStore::open_with_policy(backend, authority, limits, policy) + .await?, OwnedBackend::File(concrete), )) } @@ -321,7 +326,8 @@ impl EventlogRecordedStoreOwner { .map_err(store_open)?; let backend: Arc = concrete.clone(); Ok(( - EventlogRecordedStore::open(backend, authority, limits).await?, + EventlogRecordedStore::open_with_policy(backend, authority, limits, policy) + .await?, OwnedBackend::Sqlite(concrete), )) } @@ -342,7 +348,8 @@ impl EventlogRecordedStoreOwner { .map_err(store_open)?; let backend: Arc = concrete.clone(); Ok(( - EventlogRecordedStore::open(backend, authority, limits).await?, + EventlogRecordedStore::open_with_policy(backend, authority, limits, policy) + .await?, OwnedBackend::Sqlite(concrete), )) } @@ -368,7 +375,8 @@ impl EventlogRecordedStoreOwner { .map_err(store_open)?; let backend: Arc = concrete.clone(); Ok(( - EventlogRecordedStore::open(backend, authority, limits).await?, + EventlogRecordedStore::open_with_policy(backend, authority, limits, policy) + .await?, OwnedBackend::Postgres(concrete), )) } @@ -399,7 +407,8 @@ impl EventlogRecordedStoreOwner { .map_err(store_open)?; let backend: Arc = concrete.clone(); Ok(( - EventlogRecordedStore::open(backend, authority, limits).await?, + EventlogRecordedStore::open_with_policy(backend, authority, limits, policy) + .await?, OwnedBackend::Postgres(concrete), )) } @@ -631,6 +640,16 @@ impl OwnedBackend { } trait WorkerDriver: Send { + fn scoped<'a>( + &'a self, + _read: &'a ScopedRead, + ) -> BoxFuture<'a, Result> { + Box::pin(async { + Err(AsyncStoreError::Backend( + "scoped reads are unavailable".into(), + )) + }) + } fn load<'a>( &'a self, subject: &'a Subject, @@ -693,6 +712,33 @@ struct ProductionDriver { } impl WorkerDriver for ProductionDriver { + fn scoped<'a>( + &'a self, + read: &'a ScopedRead, + ) -> BoxFuture<'a, Result> { + Box::pin(async move { + match read { + ScopedRead::Load(subject) => self + .store + .scoped_state(subject) + .await + .map(ScopedAnswer::State), + ScopedRead::Record(id) => self + .store + .scoped_record(id) + .await + .map(|record| ScopedAnswer::Record(Box::new(record))), + ScopedRead::Batch(key) => { + self.store.scoped_batch(key).await.map(ScopedAnswer::Batch) + } + ScopedRead::Histories(subjects) => self + .store + .scoped_histories(subjects) + .await + .map(ScopedAnswer::Histories), + } + }) + } fn load<'a>( &'a self, subject: &'a Subject, @@ -1000,7 +1046,26 @@ impl Cell { } } +pub(crate) enum ScopedRead { + Load(Subject), + Record(String), + Batch(BatchKey), + Histories(Vec), +} + +pub(crate) enum ScopedAnswer { + State(Option), + Record(Box>), + Batch(Option), + Histories(Vec), +} + enum Request { + Scoped( + ScopedRead, + BridgeReadKind, + Arc>>, + ), Load( Subject, Arc, SyncReadError>>>, @@ -1061,6 +1126,7 @@ enum Request { impl Request { fn phase(&self) -> Option<&AtomicU8> { match self { + Self::Scoped(_, _, c) => Some(&c.phase), Self::Load(_, c) => Some(&c.phase), Self::LookupRecord(_, c) => Some(&c.phase), Self::LookupBatch(_, c) => Some(&c.phase), @@ -1077,6 +1143,7 @@ impl Request { } fn identity(&self) -> Option { match self { + Self::Scoped(_, kind, _) => Some(BridgeOperationIdentity::Read(*kind)), Self::Load(..) => Some(BridgeOperationIdentity::Read(BridgeReadKind::Load)), Self::LookupRecord(..) => { Some(BridgeOperationIdentity::Read(BridgeReadKind::LookupRecord)) @@ -1101,6 +1168,9 @@ impl Request { } fn cancel_closed(self) { match self { + Self::Scoped(_, _, c) => { + c.complete(Err(SyncReadError::Rejected(BridgeRejection::Closed))) + } Self::Load(_, c) => c.complete(Err(SyncReadError::Rejected(BridgeRejection::Closed))), Self::LookupRecord(_, c) => { c.complete(Err(SyncReadError::Rejected(BridgeRejection::Closed))) @@ -1133,6 +1203,7 @@ impl Request { fn cancel_stopped(self) { let rejection = BridgeRejection::WorkerStoppedBeforeDispatch; match self { + Self::Scoped(_, _, c) => c.complete(Err(SyncReadError::Rejected(rejection))), Self::Load(_, c) => c.complete(Err(SyncReadError::Rejected(rejection))), Self::LookupRecord(_, c) => c.complete(Err(SyncReadError::Rejected(rejection))), Self::LookupBatch(_, c) => c.complete(Err(SyncReadError::Rejected(rejection))), @@ -1151,6 +1222,9 @@ impl Request { #[cfg(test)] fn cancel_after_dispatch_stopped(self) { match self { + Self::Scoped(_, _, c) => c.complete(Err(SyncReadError::AfterDispatch( + BridgeAfterDispatch::WorkerStopped, + ))), Self::Load(_, c) => c.complete(Err(SyncReadError::AfterDispatch( BridgeAfterDispatch::WorkerStopped, ))), @@ -1214,10 +1288,25 @@ impl RecordedEventlogBridge { registry: Registry, owner: EventlogRecordedStoreOwner, config: BridgeConfig, + ) -> Result { + Self::start_with_read_policy( + registry, + owner, + config, + crate::CapturePolicy::FullVerification, + ) + } + + /// Starts with an explicit read-integrity policy; opening always verifies the whole authority. + pub fn start_with_read_policy( + registry: Registry, + owner: EventlogRecordedStoreOwner, + config: BridgeConfig, + policy: crate::CapturePolicy, ) -> Result { Self::start_with(config, move |runtime| { let (store, backend) = runtime - .block_on(owner.open()) + .block_on(owner.open_with_policy(policy)) .map_err(BridgeStartError::Open)?; Ok(Box::new(ProductionDriver { registry, @@ -1418,6 +1507,21 @@ impl RecordedEventlogBridge { ) } + pub(crate) fn scoped_read( + &self, + read: ScopedRead, + kind: BridgeReadKind, + wait: CallWait, + ) -> Result { + let cell = Cell::new(); + self.submit( + Request::Scoped(read, kind, cell.clone()), + &cell, + wait, + BridgeOperationIdentity::Read(kind), + ) + } + fn submit( &self, request: Request, @@ -2008,6 +2112,7 @@ fn drive_request( request: Request, ) -> bool { match request { + Request::Scoped(read, _, c) => poll_read(c, || runtime.block_on(driver.scoped(&read))), Request::Load(v, c) => poll_read(c, || runtime.block_on(driver.load(&v))), Request::LookupRecord(v, c) => poll_read(c, || runtime.block_on(driver.lookup_record(&v))), Request::LookupBatch(v, c) => poll_read(c, || runtime.block_on(driver.lookup_batch(&v))), diff --git a/crates/entity-eventlog/tests/shared_clock_cost.rs b/crates/entity-eventlog/tests/shared_clock_cost.rs new file mode 100644 index 00000000..3c331f04 --- /dev/null +++ b/crates/entity-eventlog/tests/shared_clock_cost.rs @@ -0,0 +1,428 @@ +//! Release probe for shared-batch closure growth on real SQLite authority. +//! +//! Run explicitly with `cargo +1.91.0 test --release -p entity-eventlog +//! --features sqlite,sync-bridge --test shared_clock_cost -- --ignored --nocapture`. +//! Seeding uses public kernel/canonical-byte/provider APIs and the real ER projector; +//! a complete cold open validates the fixture before any measurement. +#![cfg(all(feature = "sqlite", feature = "sync-bridge"))] + +use entity_core::{Registry, Runtime}; +use entity_eventlog::{ + AsyncBindingProvisioner, Authority, CapturePolicy, ErRecordedProjector, + EventlogBindingProvisioner, EventlogOperationContext, EventlogRecordedStore, + RecordedProviderFacade, projection_specs, + sync::{BridgeConfig, CallWait, EventlogRecordedStoreOwner, ShutdownMode, ShutdownOutcome}, +}; +use entity_executor::{BatchAction, ExecuteRequest}; +use entity_store::{ + Expect, RecordedCommit, Recording, + asynchronous::{ + AppendMember, AsyncRecordedReader, BatchKey, RecordedEntry, Subject, + batch_comparison_bytes, canonical_domain_bytes, original_request_comparison_bytes, + record_comparison_bytes, + }, +}; +use eventlog_core::{ + AppendGroup, AtomicEventStore, CaptureLimits, CommandMeta, ConsistentTenantCapture, EventStore, + Expected, InlineProjectionAdmin, NewEvent, NoGuard, StreamAppend, StreamId, TenantId, +}; +use serde_json::{Value, json}; +use sha2::{Digest, Sha256}; +use std::{ + collections::BTreeMap, + num::NonZeroU16, + path::Path, + sync::Arc, + time::{Duration, Instant}, +}; +use time::OffsetDateTime; + +const PREFIX: &str = "shared_clock_cost"; +const LIMITS: CaptureLimits = CaptureLimits { + max_events: 10_000, + max_blobs: 40_000, + max_projection_rows: 40_000, + max_payload_bytes: 512 * 1024 * 1024, +}; + +fn registry() -> Registry { + let mut fields = serde_json::Map::new(); + fields.insert("title".into(), json!({"type":"string", "required":true})); + for index in 0..96 { + fields.insert( + format!("metadata_field_{index:04}_carrying_a_long_declared_name"), + json!({"type":"string", "max_length":64}), + ); + } + let mut registry = Registry::new(); + registry + .register( + serde_json::from_value(json!({ + "entity":"metadata", "version":1, "schema":{"fields":fields}, + "lifecycle":{"initial":"open", "states":["open"]}, + "operations":{"touch":{"transitions":[{"from":"open", "to":"open"}], "emits":[]}} + })) + .unwrap(), + ) + .unwrap(); + registry +} + +fn recording(id: &str) -> Recording { + Recording { + record_id: id.into(), + recorded_at: "2026-10-03T00:00:00Z".into(), + correlation: None, + causation: None, + actor: None, + } +} +fn context(id: &str) -> EventlogOperationContext { + EventlogOperationContext { + subject: "cost-probe".into(), + actor: "entity-eventlog-test".into(), + request_id: format!("request-{id}"), + trace_id: format!("trace-{id}"), + causation_id: None, + causation_depth: 0, + occurred_at: OffsetDateTime::UNIX_EPOCH, + } +} +fn framed(domain: &str, bytes: &[u8]) -> String { + let mut hash = Sha256::new(); + hash.update(domain.as_bytes()); + hash.update([0]); + hash.update((bytes.len() as u64).to_be_bytes()); + hash.update(bytes); + format!("sha256:{:x}", hash.finalize()) +} +fn key(domain: &str, value: Value) -> String { + let wrapped = canonical_domain_bytes("_", value).unwrap(); + framed(domain, &wrapped[5..wrapped.len() - 1]) +} + +// Fixture wire envelopes intentionally live only in this test. Real projector and cold ER +// verification must accept every generated byte; measured operations use no fixture shortcut. +async fn seed_group( + backend: &eventlog_sqlite::SqliteEventStore, + authority: &Authority, + index: usize, + members: Vec, +) { + let batch_key = BatchKey::Named(format!("seed-{index}")); + let batch_wire = json!(["named", format!("seed-{index}")]); + let batch_bytes = batch_comparison_bytes(&batch_key, &members).unwrap(); + let batch_digest = framed("er.eventlog.batch-blob-key/1", &batch_bytes); + let mut blobs = vec![(batch_digest.clone(), batch_bytes)]; + let tenant = TenantId::new(&authority.tenant).unwrap(); + let mut appends = Vec::new(); + for (member_index, member) in members.iter().enumerate() { + let subject = member.entry.subject(); + let record_bytes = record_comparison_bytes(&member.entry).unwrap(); + let record_digest = framed("er.eventlog.record-blob-key/1", &record_bytes); + let request_digest = framed("er.eventlog.request-blob-key/1", &member.request_bytes); + let wrapper = canonical_domain_bytes( + "er.eventlog.recorded-entry/1", + json!({ + "authority":authority, "batch_blob":batch_digest, "batch_key":batch_wire, + "member_index":member_index, "record_blob":record_digest, + "request_blob":request_digest, "subject":[subject.entity, subject.id], + }), + ) + .unwrap(); + let wrapper_digest = framed("er.eventlog.recorded-entry-blob-key/1", &wrapper); + blobs.extend([ + (record_digest, record_bytes), + (request_digest, member.request_bytes.clone()), + (wrapper_digest.clone(), wrapper), + ]); + let stream_key = key( + "er.eventlog.subject-stream-key/1", + json!({"authority":authority, "subject":[subject.entity, subject.id]}), + ); + appends.push(StreamAppend { + stream: StreamId::new(tenant.clone(), "er.subject", stream_key).unwrap(), + expected: match member.expect { + Expect::Absent => Expected::NoStream, + Expect::Revision(revision) => Expected::Exact(revision), + }, + events: vec![ + NewEvent::new("er.recorded_entry", 1, json!({"blob":wrapper_digest})).unwrap(), + ], + }); + } + let ctx = context(&format!("seed-{index}")); + let group = AppendGroup { + tenant, + appends, + meta: CommandMeta { + idempotency_key: key( + "er.eventlog.batch-command-key/1", + json!({"authority":authority,"batch_key":batch_wire}), + ), + request_hash: batch_digest, + subject: ctx.subject, + actor: ctx.actor, + request_id: ctx.request_id, + trace_id: ctx.trace_id, + causation_id: None, + causation_depth: 0, + occurred_at: ctx.occurred_at, + claim: None, + }, + }; + let result = backend + .append_group_guarded_with_blobs(&group, Arc::new(NoGuard), &blobs) + .await + .expect("fixture group/projector commits"); + assert!(!result.deduplicated); + assert_eq!(result.appends.len(), 2); +} + +async fn seeded(path: &Path, registry: &Registry, total_events: usize) -> (Authority, u64) { + assert_eq!(total_events % 2, 1); + let backend = Arc::new( + eventlog_sqlite::SqliteEventStore::open(path.to_str().unwrap(), PREFIX) + .await + .unwrap(), + ); + let tenant = TenantId::new("shared-clock").unwrap(); + let authority = Authority { + logical_scope: "shared-clock-scope".into(), + tenant: tenant.as_str().into(), + stream_identity: backend.stream_identity(&tenant).await.unwrap(), + }; + let projector = Arc::new(ErRecordedProjector::new()); + backend.create_projections(projector.clone()).await.unwrap(); + backend.attach_inline_existing(projector).await.unwrap(); + EventlogBindingProvisioner::new(backend.clone(), LIMITS) + .provision_binding(authority.clone(), context("binding")) + .await + .unwrap(); + let runtime = Runtime::new(registry); + let mut clock = None; + let groups = (total_events - 1) / 2; + for index in 0..groups { + let decision = match clock.as_ref() { + None => runtime.create("metadata", 1, "clock", json!({"title":"clock"})), + Some(instance) => runtime.execute(instance, "touch", json!({})), + } + .unwrap(); + let expected = clock + .as_ref() + .map_or(Expect::Absent, |state: &entity_core::EntityInstance| { + Expect::Revision(state.revision) + }); + clock = Some(decision.instance.clone()); + let clock_entry = RecordedEntry::Decision( + RecordedCommit::new(decision, &recording(&format!("clock-{index}"))).unwrap(), + ); + let peer_id = format!("peer-{index}"); + let peer_entry = RecordedEntry::Decision( + RecordedCommit::new( + runtime + .create("metadata", 1, &peer_id, json!({"title":peer_id})) + .unwrap(), + &recording(&format!("peer-{index}")), + ) + .unwrap(), + ); + let members = [(clock_entry, expected), (peer_entry, Expect::Absent)] + .into_iter() + .map(|(entry, expect)| { + let bytes = original_request_comparison_bytes(&entry).unwrap(); + AppendMember::new(expect, entry, bytes) + }) + .collect(); + seed_group(&backend, &authority, index, members).await; + } + let capture = backend + .capture_tenant(&tenant, projection_specs(), LIMITS) + .await + .unwrap(); + assert_eq!( + capture.events.len(), + total_events, + "includes one authority-binding event" + ); + println!( + "seed events={total_events} recorded_entries={} groups={groups} blob_bytes={}", + total_events - 1, + capture + .blobs + .iter() + .map(|blob| blob.bytes.len()) + .sum::() + ); + drop(capture); + let verified = EventlogRecordedStore::open_with_policy( + backend, + authority.clone(), + LIMITS, + CapturePolicy::ProviderTracked, + ) + .await + .expect("cold ER verification validates all fixture bytes and histories"); + let snapshot = verified + .complete_snapshot(&authority.logical_scope) + .await + .unwrap(); + assert_eq!( + snapshot + .histories + .iter() + .map(|h| h.history.records.len()) + .sum::(), + total_events - 1 + ); + assert_eq!(snapshot.histories.len(), groups + 1); + let before = verified.calls(); + let start = Instant::now(); + let unchanged = verified + .complete_snapshot(&authority.logical_scope) + .await + .unwrap(); + let unchanged_time = start.elapsed(); + assert_eq!(unchanged, snapshot); + assert_eq!(before.captures, 1, "cold open remains fully verified"); + assert_eq!( + verified.calls(), + before, + "unchanged capture does no new verification work" + ); + println!( + "unchanged events={total_events} elapsed={unchanged_time:?} calls_before={before:?} calls_after={:?}", + verified.calls() + ); + (authority, groups as u64) +} + +fn median(samples: &mut [Duration]) -> Duration { + samples.sort(); + samples[samples.len() / 2] +} +fn measured(f: impl FnOnce() -> T) -> (T, Duration) { + let start = Instant::now(); + let output = f(); + (output, start.elapsed()) +} + +#[test] +#[ignore = "release performance probe; run explicitly on a quiet host"] +fn warm_shared_clock_batches_remain_within_twice_small_store_cost() { + assert!(!cfg!(debug_assertions), "this probe requires --release"); + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/issue-51-scratch/perf"); + std::fs::create_dir_all(&root).unwrap(); + let directory = tempfile::tempdir_in(root).unwrap(); + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + let registry = registry(); + let mut batch_medians = Vec::new(); + for count in [55, 601, 1203] { + let path = directory.path().join(format!("events-{count}.sqlite3")); + let seed_start = Instant::now(); + let (authority, mut revision) = runtime.block_on(seeded(&path, ®istry, count)); + println!( + "seed_and_verify events={count} elapsed={:?}", + seed_start.elapsed() + ); + let (mut facade, open_elapsed) = measured(|| { + RecordedProviderFacade::start_with_read_policy( + registry.clone(), + EventlogRecordedStoreOwner::Sqlite { + path: path.to_string_lossy().into_owned(), + prefix: PREFIX.into(), + authority, + limits: LIMITS, + }, + BridgeConfig { + queue_capacity: NonZeroU16::new(8).unwrap(), + }, + CapturePolicy::ProviderTracked, + ) + .unwrap() + }); + let clock = Subject::new("metadata", "clock").unwrap(); + let mut batches = Vec::new(); + let mut histories = Vec::new(); + let mut lookups = Vec::new(); + let mut snapshots = Vec::new(); + for sample in 0..6 { + let label = format!("measured-{count}-{sample}"); + let key = BatchKey::Named(label.clone()); + let request = ExecuteRequest { + subject: clock.clone(), + expected_revision: revision, + operation: "touch".into(), + arguments: json!({}), + fulfillments: BTreeMap::new(), + recording: recording(&label), + }; + let (outcome, batch_time) = measured(|| { + facade + .execute_batch( + context(&label), + key.clone(), + vec![BatchAction::Execute(request)], + CallWait::Forever, + ) + .unwrap() + }); + assert!(!outcome.replayed()); + revision += 1; + let (history, history_time) = measured(|| { + facade + .scoped() + .read_history(&clock, CallWait::Forever) + .unwrap() + }); + assert_eq!(history.records.len() as u64, revision); + let (lookup, lookup_time) = measured(|| { + facade + .scoped() + .lookup_batch(&key, CallWait::Forever) + .unwrap() + .unwrap() + }); + assert_eq!(lookup.records.len(), 1); + let (snapshot, snapshot_time) = + measured(|| facade.complete_snapshot(CallWait::Forever).unwrap()); + let terminal = snapshot + .histories + .iter() + .find(|h| h.history.subject == clock) + .unwrap(); + assert_eq!(terminal.terminal.revision, revision); + println!( + "sample events={count} round={sample} batch={batch_time:?} history={history_time:?} lookup={lookup_time:?} snapshot={snapshot_time:?}" + ); + if sample > 0 { + batches.push(batch_time); + histories.push(history_time); + lookups.push(lookup_time); + snapshots.push(snapshot_time); + } + } + let batch = median(&mut batches); + println!( + "median initial_events={count} open={open_elapsed:?} batch={batch:?} history={:?} lookup={:?} snapshot={:?}", + median(&mut histories), + median(&mut lookups), + median(&mut snapshots) + ); + batch_medians.push((count, batch)); + assert_eq!( + facade.shutdown(ShutdownMode::Drain, CallWait::Forever), + ShutdownOutcome::Joined { provider: Ok(()) } + ); + } + let small = batch_medians[0].1; + for (count, median) in &batch_medians[1..] { + assert!( + *median <= small * 2, + "shared-clock batch at {count} events costs {median:?}, more than twice 55-event {small:?}; all {batch_medians:?}" + ); + } +} diff --git a/crates/entity-executor/Cargo.toml b/crates/entity-executor/Cargo.toml index 130c9189..a9615dd9 100644 --- a/crates/entity-executor/Cargo.toml +++ b/crates/entity-executor/Cargo.toml @@ -8,8 +8,8 @@ license.workspace = true repository.workspace = true [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } -entity-store = { path = "../entity-store", version = "0.25.1" } +entity-core = { path = "../entity-core", version = "0.26.0" } +entity-store = { path = "../entity-store", version = "0.26.0" } serde_json.workspace = true [lints] diff --git a/crates/entity-executor/src/lib.rs b/crates/entity-executor/src/lib.rs index 8fcb8387..f1060bc2 100644 --- a/crates/entity-executor/src/lib.rs +++ b/crates/entity-executor/src/lib.rs @@ -67,6 +67,83 @@ pub enum BatchAction { Merge(MergeRequest), } +/// A batch action whose operation definition is selected before reading subject state. +/// +/// Explicit versions never select the latest registry definition. Existing [`BatchAction`] +/// callers retain their row-derived definition authority. +#[derive(Debug, Clone, PartialEq)] +pub enum VersionedBatchAction { + /// Create using the version already carried by the request. + Create(CreateRequest), + /// Execute under the caller's exact definition version. + Execute { + /// Positive registered definition version. + definition_version: u32, + /// Operation and predecessor supplied by the caller. + request: ExecuteRequest, + }, + /// Append non-state-changing evidence at an exact revision. + Observe(RecordedObservation), + /// Select input refusals before reading the merge's history. + Merge { + /// Positive registered definition version. + definition_version: u32, + /// Operation and merge head supplied by the caller. + request: MergeRequest, + }, +} + +struct BoundAction { + action: BatchAction, + definition_version: Option, +} + +impl std::ops::Deref for BoundAction { + type Target = BatchAction; + + fn deref(&self) -> &Self::Target { + &self.action + } +} + +impl From for BoundAction { + fn from(action: BatchAction) -> Self { + Self { + action, + definition_version: None, + } + } +} + +impl From for BoundAction { + fn from(action: VersionedBatchAction) -> Self { + match action { + VersionedBatchAction::Create(request) => BatchAction::Create(request).into(), + VersionedBatchAction::Observe(observation) => BatchAction::Observe(observation).into(), + VersionedBatchAction::Execute { + definition_version, + request, + } => Self { + action: BatchAction::Execute(request), + definition_version: Some(definition_version), + }, + VersionedBatchAction::Merge { + definition_version, + request, + } => Self { + action: BatchAction::Merge(request), + definition_version: Some(definition_version), + }, + } + } +} + +#[derive(Default)] +struct PreparedAction<'a> { + operation: Option>, + creation: Option>, +} + /// One merge decision over a forked subject. #[derive(Debug, Clone, PartialEq)] pub struct MergeRequest { @@ -242,6 +319,9 @@ impl<'a> Executor<'a> { /// Executes and records one operation under its record-id single namespace. /// + /// The loaded row selects the definition version. Use [`Self::execute_versioned`] when + /// input refusals must precede subject existence and revision checks. + /// /// # Errors /// /// Invalid input, kernel refusal, conflicting or corrupt evidence, or append failure. @@ -250,6 +330,48 @@ impl<'a> Executor<'a> { self.batch(key, vec![BatchAction::Execute(request)]).await } + /// Executes with an explicit definition, deciding input refusals before loading state. + /// + /// Exact retries use their saved definition before consulting the current registry. + /// + /// # Errors + /// + /// Invalid version or input, kernel refusal, definition/predecessor mismatch, conflicting + /// or corrupt recorded identity, or append failure. + pub async fn execute_versioned( + &self, + request: ExecuteRequest, + definition_version: u32, + ) -> Result { + let key = BatchKey::SingleRecord(request.recording.record_id.clone()); + self.batch_versioned( + key, + vec![VersionedBatchAction::Execute { + definition_version, + request, + }], + ) + .await + } + + /// Executes an atomic batch with caller-selected operation definition versions. + /// + /// Each fresh operation's input refusal is evaluated before its state or merge-history + /// read. Empty batches are inert; saved identities are recovered before current definitions. + /// + /// # Errors + /// + /// Invalid version or input, kernel refusal, definition/predecessor mismatch, conflicting + /// or corrupt recorded identity, or append failure. + pub async fn batch_versioned( + &self, + key: BatchKey, + actions: Vec, + ) -> Result { + self.batch_bound(key, actions.into_iter().map(BoundAction::from).collect()) + .await + } + /// Records one non-state-changing observation under its record-id single namespace. /// /// # Errors @@ -268,6 +390,8 @@ impl<'a> Executor<'a> { /// /// Identity and batch retries are recovered and verified before current state or registry /// lookup. An empty batch is inert and does not validate or consume `key`. + /// Operations derive their definition versions from loaded rows; use + /// [`Self::batch_versioned`] for input refusals independent of subject existence. /// /// # Errors /// @@ -276,6 +400,15 @@ impl<'a> Executor<'a> { &self, key: BatchKey, actions: Vec, + ) -> Result { + self.batch_bound(key, actions.into_iter().map(BoundAction::from).collect()) + .await + } + + async fn batch_bound( + &self, + key: BatchKey, + actions: Vec, ) -> Result { if actions.is_empty() { return Ok(AppendOutcome::Empty); @@ -284,6 +417,12 @@ impl<'a> Executor<'a> { let mut ids = std::collections::BTreeSet::new(); for action in &actions { action.validate_shape()?; + if action.definition_version == Some(0) { + return Err(AsyncStoreError::InvalidInput( + "execute requires a positive definition version".to_owned(), + ) + .into()); + } if !ids.insert(action.record_id()) { return Err(ExecutionError::Store(AsyncStoreError::DuplicateRecordId { record_id: action.record_id().to_owned(), @@ -307,8 +446,10 @@ impl<'a> Executor<'a> { if let Some(reason) = refusal_reason(error) { let refusal = RecordedRefusal { key: key.clone(), - subjects: actions.iter().map(BatchAction::subject).collect(), - request: serde_json::Value::Array(actions.iter().map(action_value).collect()), + subjects: actions.iter().map(|action| action.subject()).collect(), + request: serde_json::Value::Array( + actions.iter().map(bound_action_value).collect(), + ), recording: first_recording(&actions[0]), reason, }; @@ -321,22 +462,84 @@ impl<'a> Executor<'a> { decided } + fn prepare(&self, action: &BoundAction) -> Result, ExecutionError> { + match &action.action { + BatchAction::Create(request) => { + let creation = Runtime::new(self.registry).decide_create( + &request.subject.entity, + request.definition_version, + request.subject.id.clone(), + request.fields.clone(), + ); + if let Ok(Evaluation::Refused(refusal)) = creation { + return Err(CoreError::Refused { + outcome: refusal.outcome, + error: refusal.error, + message: refusal.message, + } + .into()); + } + // Only declared refusals precede existence. Preserve other creation errors' + // existing ordering, and reuse this decision if the subject is absent. + Ok(PreparedAction { + creation: Some(creation), + operation: None, + }) + } + BatchAction::Execute(request) + | BatchAction::Merge(MergeRequest { + execute: request, .. + }) => { + let Some(version) = action.definition_version else { + return Ok(PreparedAction::default()); + }; + let definition = self + .registry + .get(&request.subject.entity, version) + .ok_or_else(|| CoreError::EntityNotRegistered { + entity: request.subject.entity.clone(), + version, + })?; + match entity_core::decide_before_load( + definition, + request.subject.id.clone(), + &request.operation, + request.arguments.clone(), + )? { + PreloadDecision::Load(operation) => Ok(PreparedAction { + operation: Some(operation), + creation: None, + }), + PreloadDecision::Refused(refusal) => Err(CoreError::Refused { + outcome: refusal.outcome, + error: refusal.error, + message: refusal.message, + } + .into()), + } + } + BatchAction::Observe(_) => Ok(PreparedAction::default()), + } + } + /// Decides every action on the state the store holds and appends the result as one batch. async fn decide_and_append( &self, key: &BatchKey, - actions: &[BatchAction], + actions: &[BoundAction], ) -> Result { let key = key.clone(); let mut overlay: BTreeMap> = BTreeMap::new(); let mut members = Vec::with_capacity(actions.len()); for action in actions { + let prepared = self.prepare(action)?; let subject = action.subject(); - if let BatchAction::Merge(request) = action { + if let BatchAction::Merge(request) = &action.action { let merge = self.merge_base(request).await?; let (expect, entry) = self.decide( &BatchAction::Execute(request.execute.clone()), Some(&merge.base), + prepared, )?; let request_bytes = request_comparison_bytes( &BatchAction::Execute(request.execute.clone()), @@ -356,7 +559,7 @@ impl<'a> Executor<'a> { .get(&subject) .expect("subject was loaded before decision") .as_ref(); - let (expect, entry) = self.decide(action, current)?; + let (expect, entry) = self.decide(action, current, prepared)?; let request_bytes = request_comparison_bytes(action, &entry)?; let next = match &entry { RecordedEntry::Decision(commit) => Some(commit.instance.clone()), @@ -433,6 +636,7 @@ impl<'a> Executor<'a> { &self, action: &BatchAction, current: Option<&EntityInstance>, + prepared: PreparedAction<'_>, ) -> Result<(Expect, RecordedEntry), ExecutionError> { match action { BatchAction::Create(request) => { @@ -443,12 +647,10 @@ impl<'a> Executor<'a> { found: Some(current.revision), })); } - let decision = Runtime::new(self.registry).create( - &request.subject.entity, - request.definition_version, - request.subject.id.clone(), - request.fields.clone(), - )?; + let decision = prepared + .creation + .expect("create was prepared")? + .into_decision()?; let commit = RecordedCommit::new(decision, &request.recording).map_err(|error| { ExecutionError::Store(AsyncStoreError::InvalidInput(error.to_string())) @@ -471,39 +673,38 @@ impl<'a> Executor<'a> { })); } let runtime = Runtime::new(self.registry); - let prepared = match runtime.decide_before_load( - &request.subject.entity, - current.version, - request.subject.id.clone(), - &request.operation, - request.arguments.clone(), - )? { - PreloadDecision::Load(prepared) => prepared, - PreloadDecision::Refused(refusal) => { - return Err(CoreError::Refused { - outcome: refusal.outcome, - error: refusal.error, - message: refusal.message, + let prepared = match prepared.operation { + Some(prepared) => prepared, + None => match runtime.decide_before_load( + &request.subject.entity, + current.version, + request.subject.id.clone(), + &request.operation, + request.arguments.clone(), + )? { + PreloadDecision::Load(prepared) => prepared, + PreloadDecision::Refused(refusal) => { + return Err(CoreError::Refused { + outcome: refusal.outcome, + error: refusal.error, + message: refusal.message, + } + .into()) } - .into()) - } + }, }; let evaluation = match prepared.select_with(current)? { LoadedDecision::Complete(evaluation) => { - if !request.fulfillments.is_empty() { - let outcome = match &evaluation { - Evaluation::Accepted(decision) => { - decision.record.outcome.clone().unwrap_or_default() + if let Evaluation::Accepted(decision) = &evaluation { + if !request.fulfillments.is_empty() { + return Err(CoreError::FulfillmentKeysMismatch { + operation: request.operation.clone(), + outcome: decision.record.outcome.clone().unwrap_or_default(), + missing: Vec::new(), + extra: request.fulfillments.keys().cloned().collect(), } - Evaluation::Refused(_) => String::new(), - }; - return Err(CoreError::FulfillmentKeysMismatch { - operation: request.operation.clone(), - outcome, - missing: Vec::new(), - extra: request.fulfillments.keys().cloned().collect(), + .into()); } - .into()); } evaluation } @@ -521,9 +722,11 @@ impl<'a> Executor<'a> { RecordedEntry::Decision(commit), )) } - BatchAction::Merge(request) => { - self.decide(&BatchAction::Execute(request.execute.clone()), current) - } + BatchAction::Merge(request) => self.decide( + &BatchAction::Execute(request.execute.clone()), + current, + prepared, + ), BatchAction::Observe(observation) => { let subject = action.subject(); let current = current.ok_or_else(|| { @@ -551,7 +754,7 @@ impl<'a> Executor<'a> { async fn recover_existing( &self, key: &BatchKey, - actions: &[BatchAction], + actions: &[BoundAction], ) -> Result, ExecutionError> { if let Some(batch) = self.store.lookup_batch(key).await? { validate_stored_batch(key, actions.len(), &batch)?; @@ -626,9 +829,10 @@ impl<'a> Executor<'a> { fn match_committed( &self, - action: &BatchAction, + action: &BoundAction, stored: &StoredRecord, ) -> Result<(), ExecutionError> { + match_saved_version(action, &stored.entry)?; let subject = action.subject(); let requested = request_comparison_bytes(action, &stored.entry) .map_err(|error| map_retry_error(&subject, action.record_id(), error, false))?; @@ -642,9 +846,10 @@ impl<'a> Executor<'a> { fn match_imported( &self, - action: &BatchAction, + action: &BoundAction, evidence: &entity_store::asynchronous::ImportedRecordEvidence, ) -> Result<(), ExecutionError> { + match_saved_version(action, &evidence.entry)?; let subject = action.subject(); let requested = request_comparison_bytes(action, &evidence.entry) .map_err(|error| map_retry_error(&subject, action.record_id(), error, true))?; @@ -691,6 +896,38 @@ impl<'a> Executor<'a> { } } +fn match_saved_version(action: &BoundAction, entry: &RecordedEntry) -> Result<(), ExecutionError> { + if let Some(version) = action.definition_version { + let matches = match entry { + RecordedEntry::Decision(commit) => { + commit.instance.version == version + && commit + .envelope + .record + .definition + .as_ref() + .is_none_or(|definition| definition.version == version) + } + RecordedEntry::Observation(_) => false, + }; + if !matches { + return Err(AsyncStoreError::RecordConflict { + record_id: action.record_id().to_owned(), + } + .into()); + } + } + Ok(()) +} + +fn bound_action_value(action: &BoundAction) -> Value { + let mut value = action_value(action); + if let Some(version) = action.definition_version { + value["definition_version"] = version.into(); + } + value +} + fn validate_stored_batch( key: &BatchKey, action_count: usize, diff --git a/crates/entity-executor/tests/input_refusal_precedence.rs b/crates/entity-executor/tests/input_refusal_precedence.rs new file mode 100644 index 00000000..353f6187 --- /dev/null +++ b/crates/entity-executor/tests/input_refusal_precedence.rs @@ -0,0 +1,469 @@ +//! Input refusal ordering and explicit definition authority for recorded execution. +use std::collections::BTreeMap; + +use entity_core::{CoreError, Registry}; +use entity_executor::{ + test_support::block_on, CreateRequest, ExecuteRequest, ExecutionError, Executor, +}; +use entity_store::{ + asynchronous::{AsyncRecordedReader, MemoryRecordedStore, Subject}, + Recording, +}; +use serde_json::json; + +fn registry() -> Registry { + let mut registry = Registry::new(); + for version in [1, 2] { + registry.register(serde_json::from_value(json!({ + "entity": "invoice", "version": version, "semantics": "service/3", + "schema": {"fields": {}}, + "lifecycle": {"initial": "Open", "states": ["Open"]}, + "create": {"arguments": {"fields": {"accept": {"type":"boolean","required":true}}}, + "outcomes": [ + {"name":"rejected", "when":{"eq":["$args.accept",false]}, + "refuses":{"error":"InputRejected","message":"Input was refused"}}, + {"name":"created","effect":"creates"} + ]}, + "operations": {"Pay": { + "arguments": {"fields": {"accept": {"type":"boolean","required":true}}}, + "outcomes": [ + {"name":"rejected", "when":{"eq":["$args.accept",false]}, + "refuses":{"error":"InputRejected","message":"Input was refused"}}, + {"name":"paid","effect":"updates"} + ]}} + })).unwrap()).unwrap(); + } + registry +} +fn recording(id: &str) -> Recording { + Recording { + record_id: id.into(), + recorded_at: "2026-10-03T00:00:00Z".into(), + correlation: None, + causation: None, + actor: None, + } +} +fn create(id: &str, record: &str, accept: bool) -> CreateRequest { + CreateRequest { + subject: Subject::new("invoice", id).unwrap(), + definition_version: 1, + fields: json!({"accept":accept}), + recording: recording(record), + } +} +fn execute(id: &str, record: &str, accept: bool) -> ExecuteRequest { + ExecuteRequest { + subject: Subject::new("invoice", id).unwrap(), + expected_revision: 1, + operation: "Pay".into(), + arguments: json!({"accept":accept}), + fulfillments: BTreeMap::new(), + recording: recording(record), + } +} +fn assert_refused(error: ExecutionError) { + assert!( + matches!(&error,ExecutionError::Core(CoreError::Refused {outcome,error,message}) + if outcome=="rejected" && error=="InputRejected" && message.as_deref()==Some("Input was refused")), + "expected declared input refusal; got {error:?}" + ); +} +#[test] +fn executor_input_refusal_existing_create() { + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + block_on(executor.create(create("a", "created", true))).unwrap(); + let before = block_on(store.history(&Subject::new("invoice", "a").unwrap())).unwrap(); + store.clear_trace(); + assert_refused(block_on(executor.create(create("a", "refused", false))).unwrap_err()); + assert_eq!(store.trace(), vec!["lookup_batch", "lookup_record:refused"]); + assert_eq!( + block_on(store.history(&Subject::new("invoice", "a").unwrap())).unwrap(), + before + ); + assert_eq!(block_on(store.lookup_record("refused")).unwrap(), None); +} +#[test] +fn executor_input_refusal_missing_execute() { + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + assert_refused( + block_on(executor.execute_versioned(execute("missing", "refused", false), 1)).unwrap_err(), + ); + assert_eq!(store.trace(), vec!["lookup_batch", "lookup_record:refused"]); + assert_eq!(block_on(store.lookup_record("refused")).unwrap(), None); +} + +#[test] +fn versioned_refusal_precedes_existing_revision_and_definition_mismatch() { + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + block_on(executor.create(create("a", "created", true))).unwrap(); + let mut request = execute("a", "refused", false); + request.expected_revision = 99; + store.clear_trace(); + assert_refused(block_on(executor.execute_versioned(request, 2)).unwrap_err()); + assert_eq!(store.trace(), vec!["lookup_batch", "lookup_record:refused"]); +} + +#[test] +fn accepted_input_preserves_absence_revision_and_definition_checks() { + use entity_store::asynchronous::AsyncStoreError; + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + assert!(matches!( + block_on(executor.execute_versioned(execute("a", "missing", true), 1)), + Err(ExecutionError::Store(AsyncStoreError::RevisionConflict { + found: None, + .. + })) + )); + block_on(executor.create(create("a", "created", true))).unwrap(); + let mut stale = execute("a", "stale", true); + stale.expected_revision = 2; + assert!(matches!( + block_on(executor.execute_versioned(stale, 1)), + Err(ExecutionError::Store(AsyncStoreError::RevisionConflict { + found: Some(1), + .. + })) + )); + assert!(matches!( + block_on(executor.execute_versioned(execute("a", "wrong-version", true), 2)), + Err(ExecutionError::Core(CoreError::EntityMismatch { + expected_version: 2, + actual_version: 1, + .. + })) + )); + assert!(matches!( + block_on(executor.create(create("a", "duplicate", true))), + Err(ExecutionError::Store(AsyncStoreError::RevisionConflict { + found: Some(1), + .. + })) + )); + let mut malformed = create("a", "malformed", true); + malformed.fields = json!({"accept":"bad"}); + assert!(matches!( + block_on(executor.create(malformed)), + Err(ExecutionError::Store(AsyncStoreError::RevisionConflict { + found: Some(1), + .. + })) + )); +} + +#[test] +fn legacy_execute_keeps_row_authority_without_choosing_a_registered_version() { + use entity_store::asynchronous::AsyncStoreError; + for registry in [registry(), { + let mut r = Registry::new(); + let definition = registry() + .get("invoice", 1) + .unwrap() + .clone() + .into_definition(); + r.register(definition).unwrap(); + r + }] { + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + assert!(matches!( + block_on(executor.execute(execute("absent", "legacy", false))), + Err(ExecutionError::Store(AsyncStoreError::RevisionConflict { + found: None, + .. + })) + )); + block_on(executor.create(create("a", "created", true))).unwrap(); + block_on(executor.execute(execute("a", "legacy", true))).unwrap(); + } +} + +#[test] +fn versioned_batch_refusal_rolls_back_earlier_local_decisions() { + use entity_executor::VersionedBatchAction as Action; + use entity_store::asynchronous::{AsyncStateReader, BatchKey}; + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + let key = BatchKey::Named("atomic".into()); + let actions = vec![ + Action::Create(create("a", "created", true)), + Action::Execute { + definition_version: 1, + request: execute("a", "refused", false), + }, + ]; + assert_refused(block_on(executor.batch_versioned(key.clone(), actions)).unwrap_err()); + assert!(!store.trace().iter().any(|call| call == "append")); + assert_eq!( + block_on(store.load(&Subject::new("invoice", "a").unwrap())).unwrap(), + None + ); + assert_eq!(block_on(store.lookup_record("created")).unwrap(), None); + assert_eq!(block_on(store.lookup_batch(&key)).unwrap(), None); + let actions = vec![ + Action::Create(create("a", "created", true)), + Action::Execute { + definition_version: 1, + request: execute("a", "refused", true), + }, + ]; + assert!(!block_on(executor.batch_versioned(key, actions)) + .unwrap() + .replayed()); + assert_eq!( + block_on(store.load(&Subject::new("invoice", "a").unwrap())) + .unwrap() + .unwrap() + .revision, + 2 + ); +} + +#[test] +fn explicit_definition_does_not_require_registry_for_committed_retry() { + use entity_store::asynchronous::{ + original_request_comparison_bytes, AsyncStoreError, RecordLookup, + }; + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + block_on(executor.create(create("a", "created", true))).unwrap(); + let request = execute("a", "paid", true); + let original = block_on(executor.execute_versioned(request.clone(), 1)).unwrap(); + let Some(RecordLookup::Committed(stored)) = block_on(store.lookup_record("paid")).unwrap() + else { + panic!("missing commit") + }; + assert_eq!( + stored.request_bytes, + original_request_comparison_bytes(&stored.entry).unwrap() + ); + assert!(!String::from_utf8(stored.request_bytes) + .unwrap() + .contains("definition_version")); + let empty = Registry::new(); + let restarted = Executor::new(&empty, &store); + let retry = block_on(restarted.execute_versioned(request.clone(), 1)).unwrap(); + assert!(retry.replayed()); + assert_eq!(retry.receipt(), original.receipt()); + assert!(block_on(restarted.execute(request.clone())) + .unwrap() + .replayed()); + assert!(matches!(block_on(restarted.execute_versioned(request,2)), + Err(ExecutionError::Store(AsyncStoreError::RecordConflict {record_id})) if record_id=="paid")); +} + +#[test] +fn legacy_committed_execution_can_be_retried_with_its_explicit_saved_version() { + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + block_on(executor.create(create("a", "created", true))).unwrap(); + let request = execute("a", "paid", true); + let original = block_on(executor.execute(request.clone())).unwrap(); + let empty = Registry::new(); + let retry = block_on(Executor::new(&empty, &store).execute_versioned(request, 1)).unwrap(); + assert!(retry.replayed()); + assert_eq!(retry.receipt(), original.receipt()); +} + +#[test] +fn imported_execution_retry_checks_explicit_version_before_accepting_identity() { + use entity_core::Runtime; + use entity_store::{ + asynchronous::{ + AppendOutcome, AsyncStoreError, HistoryOrigin, ImportedRecordEvidence, + KnownLegacyOrder, LegacyAnchor, LegacyCompleteness, LegacyEvidence, + LegacyOrderDeclaration, RecordedEntry, SubjectHistory, + }, + RecordedCommit, + }; + let registry = registry(); + let runtime = Runtime::new(®istry); + let created = runtime + .create("invoice", 1, "a", json!({"accept":true})) + .unwrap(); + let decision = runtime + .execute(&created.instance, "Pay", json!({"accept":true})) + .unwrap(); + let commit = RecordedCommit::new(decision, &recording("paid")).unwrap(); + let store = MemoryRecordedStore::new(); + store + .seed_imported(SubjectHistory { + subject: Subject::new("invoice", "a").unwrap(), + origin: HistoryOrigin::Imported(LegacyAnchor { + instance: commit.instance.clone(), + completeness: LegacyCompleteness::AvailableEvidenceOnly, + order: LegacyOrderDeclaration::PerKindOnly, + evidence: vec![LegacyEvidence::Envelope( + ImportedRecordEvidence::new( + RecordedEntry::Decision(commit), + "source", + "record/1", + KnownLegacyOrder::PerKind(0), + ) + .unwrap(), + )], + }), + records: vec![], + }) + .unwrap(); + let empty = Registry::new(); + let executor = Executor::new(&empty, &store); + assert!(matches!( + block_on(executor.execute_versioned(execute("a", "paid", true), 1)).unwrap(), + AppendOutcome::Historical { .. } + )); + assert!( + matches!(block_on(executor.execute_versioned(execute("a","paid",true),2)), + Err(ExecutionError::Store(AsyncStoreError::RecordConflict {record_id})) if record_id=="paid") + ); + store.remove_imported_definition_for_test("paid"); + assert!(matches!( + block_on(executor.execute_versioned(execute("a", "paid", true), 1)), + Err(ExecutionError::Store( + AsyncStoreError::HistoricalRetryUnverifiable { .. } + )) + )); +} + +#[test] +fn versioned_merge_refusal_precedes_the_merge_history_read() { + use entity_executor::{MergeRequest, VersionedBatchAction}; + use entity_store::asynchronous::BatchKey; + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + let action = VersionedBatchAction::Merge { + definition_version: 1, + request: MergeRequest { + execute: execute("missing", "refused", false), + first: "unused".into(), + }, + }; + assert_refused( + block_on(executor.batch_versioned(BatchKey::Named("merge".into()), vec![action])) + .unwrap_err(), + ); + assert_eq!(store.trace(), vec!["lookup_batch", "lookup_record:refused"]); +} + +#[test] +fn versioned_empty_and_invalid_shape_requests_perform_no_io() { + use entity_executor::VersionedBatchAction; + use entity_store::asynchronous::{AppendOutcome, AsyncStoreError, BatchKey}; + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + assert!(matches!( + block_on(executor.batch_versioned(BatchKey::Named(String::new()), vec![])).unwrap(), + AppendOutcome::Empty + )); + assert!( + matches!(block_on(executor.execute_versioned(execute("a","bad-version",false),0)), + Err(ExecutionError::Store(AsyncStoreError::InvalidInput(message))) if message.contains("definition version")) + ); + let mut bad = execute("a", "bad-shape", false); + bad.expected_revision = 0; + assert!(matches!(block_on(executor.execute_versioned(bad,1)), + Err(ExecutionError::Store(AsyncStoreError::InvalidInput(message))) if message.contains("revision"))); + let action = VersionedBatchAction::Execute { + definition_version: 1, + request: execute("a", "duplicate", false), + }; + assert!(matches!( + block_on(executor.batch_versioned( + BatchKey::Named("dupes".into()), + vec![action.clone(), action] + )), + Err(ExecutionError::Store( + AsyncStoreError::DuplicateRecordId { .. } + )) + )); + assert_eq!(store.trace(), Vec::::new()); +} + +#[test] +fn named_mixed_batch_retry_uses_saved_versions_and_rejects_changed_binding() { + use entity_executor::VersionedBatchAction as Action; + use entity_store::{ + asynchronous::{AsyncStoreError, BatchKey}, + RecordedObservation, + }; + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + let key = BatchKey::Named("mixed".into()); + let mut actions = vec![ + Action::Create(create("a", "created", true)), + Action::Observe(RecordedObservation { + entity: "invoice".into(), + id: "a".into(), + revision: 1, + envelope: recording("observed") + .seal(json!({"source":"test"})) + .unwrap(), + }), + Action::Execute { + definition_version: 1, + request: execute("a", "paid", true), + }, + ]; + let original = block_on(executor.batch_versioned(key.clone(), actions.clone())).unwrap(); + let empty = Registry::new(); + let restarted = Executor::new(&empty, &store); + let retry = block_on(restarted.batch_versioned(key.clone(), actions.clone())).unwrap(); + assert!(retry.replayed()); + assert_eq!(retry.receipt(), original.receipt()); + actions[2] = Action::Execute { + definition_version: 2, + request: execute("a", "paid", true), + }; + assert!(matches!(block_on(restarted.batch_versioned(key,actions)), + Err(ExecutionError::Store(AsyncStoreError::RecordConflict {record_id})) if record_id=="paid")); +} + +#[test] +fn recorded_refusal_keeps_explicit_definition_authority() { + use entity_store::asynchronous::{ + AsyncRefusalRecorder, AsyncStoreError, BoxFuture, RecordedRefusal, + }; + use std::sync::Mutex; + #[derive(Default)] + struct Recorder(Mutex>); + impl AsyncRefusalRecorder for Recorder { + fn record_refusal<'a>( + &'a self, + refusal: &'a RecordedRefusal, + ) -> BoxFuture<'a, Result> { + Box::pin(async move { + self.0.lock().unwrap().push(refusal.clone()); + Ok(false) + }) + } + fn refusals<'a>(&'a self) -> BoxFuture<'a, Result, AsyncStoreError>> { + Box::pin(async move { Ok(self.0.lock().unwrap().clone()) }) + } + } + let registry = registry(); + let store = MemoryRecordedStore::new(); + let recorder = Recorder::default(); + let executor = Executor::recording_refusals(®istry, &store, &recorder); + assert_refused( + block_on(executor.execute_versioned(execute("a", "refused", false), 2)).unwrap_err(), + ); + let refusals = block_on(recorder.refusals()).unwrap(); + assert_eq!(refusals.len(), 1); + assert_eq!(refusals[0].request[0]["definition_version"], json!(2)); + assert_eq!(refusals[0].request[0]["operation"], json!("Pay")); + assert_eq!(store.trace(), vec!["lookup_batch", "lookup_record:refused"]); +} diff --git a/crates/entity-executor/tests/refusal_fulfillment_review.rs b/crates/entity-executor/tests/refusal_fulfillment_review.rs new file mode 100644 index 00000000..df53a9de --- /dev/null +++ b/crates/entity-executor/tests/refusal_fulfillment_review.rs @@ -0,0 +1,144 @@ +//! Adversarial batch and malformed-action probes for declared refusal precedence. + +use std::collections::BTreeMap; + +use entity_core::{CoreError, OperationFieldAction, Registry}; +use entity_executor::{ + test_support::block_on, BatchAction, CreateRequest, ExecuteRequest, ExecutionError, Executor, +}; +use entity_store::{ + asynchronous::{AsyncRecordedReader, AsyncStateReader, BatchKey, MemoryRecordedStore, Subject}, + Recording, +}; +use serde_json::json; + +fn registry() -> Registry { + let mut registry = Registry::new(); + registry + .register( + serde_json::from_value(json!({ + "entity": "invoice", "version": 1, "semantics": "service/3", + "schema": {"fields": {"issued_at": {"type": "string", "required": true}}}, + "lifecycle": {"initial": "Draft", "states": ["Draft"]}, + "operations": {"Issue": {"outcomes": [ + {"name": "rejected", "when": {"eq": ["$fields.issued_at", "pending"]}, + "refuses": {"error": "NotAccepted", "message": "Invoice cannot be issued"}}, + {"name": "issued", "effect": "updates", + "fulfills": {"issued_at": {"actions": "required"}}} + ]}} + })) + .unwrap(), + ) + .unwrap(); + registry +} + +fn recording(id: &str) -> Recording { + Recording { + record_id: id.to_owned(), + recorded_at: "2026-10-03T00:00:00Z".to_owned(), + correlation: None, + causation: None, + actor: None, + } +} + +fn request(id: &str, revision: u64, value: serde_json::Value) -> ExecuteRequest { + ExecuteRequest { + subject: Subject::new("invoice", "i-1").unwrap(), + expected_revision: revision, + operation: "Issue".to_owned(), + arguments: json!({}), + fulfillments: BTreeMap::from([( + "issued_at".to_owned(), + OperationFieldAction::Set { value }, + )]), + recording: recording(id), + } +} + +fn seed(executor: &Executor<'_>, value: &str) { + block_on(executor.create(CreateRequest { + subject: Subject::new("invoice", "i-1").unwrap(), + definition_version: 1, + fields: json!({"issued_at": value}), + recording: recording("create"), + })) + .unwrap(); +} + +fn assert_declared(error: ExecutionError) { + assert!( + matches!(error, ExecutionError::Core(CoreError::Refused { + ref outcome, ref error, ref message + }) if outcome == "rejected" && error == "NotAccepted" + && message.as_deref() == Some("Invoice cannot be issued")), + "{error:?}" + ); +} + +#[test] +fn later_refusal_rolls_back_earlier_fulfillment_and_leaves_batch_identity_reusable() { + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + seed(&executor, "ready"); + let subject = Subject::new("invoice", "i-1").unwrap(); + let before = block_on(store.load(&subject)).unwrap(); + let history = block_on(store.history(&subject)).unwrap(); + let key = BatchKey::Named("two-steps".to_owned()); + let mut refused = request("second", 2, json!([1, 2])); + refused + .fulfillments + .insert("unknown".to_owned(), OperationFieldAction::Remove); + let bad = vec![ + BatchAction::Execute(request("first", 1, json!("pending"))), + BatchAction::Execute(refused), + ]; + for _ in 0..2 { + assert_declared(block_on(executor.batch(key.clone(), bad.clone())).unwrap_err()); + assert_eq!(block_on(store.load(&subject)).unwrap(), before); + assert_eq!(block_on(store.history(&subject)).unwrap(), history); + assert_eq!(block_on(store.lookup_record("first")).unwrap(), None); + assert_eq!(block_on(store.lookup_record("second")).unwrap(), None); + assert_eq!(block_on(store.lookup_batch(&key)).unwrap(), None); + } + let good = vec![ + BatchAction::Execute(request("first", 1, json!("ready-again"))), + BatchAction::Execute(request("second", 2, json!("issued"))), + ]; + let committed = block_on(executor.batch(key.clone(), good.clone())).unwrap(); + assert!(!committed.replayed()); + let state = block_on(store.load(&subject)).unwrap().unwrap(); + assert_eq!(state.revision, 3); + assert_eq!(state.fields["issued_at"], json!("issued")); + let replay = block_on(executor.batch(key, good)).unwrap(); + assert!(replay.replayed()); + assert_eq!(replay.receipt(), committed.receipt()); + assert_eq!(block_on(store.history(&subject)).unwrap().records.len(), 3); +} + +#[test] +fn refusal_wins_over_required_field_removal_and_invalid_value_after_restart() { + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + seed(&executor, "pending"); + let subject = Subject::new("invoice", "i-1").unwrap(); + let before = block_on(store.load(&subject)).unwrap(); + for action in [ + OperationFieldAction::Remove, + OperationFieldAction::Set { value: json!(null) }, + OperationFieldAction::Set { + value: json!({"nested": true}), + }, + ] { + let restarted = Executor::new(®istry, &store); + let mut refused = request("refused", 1, json!("unused")); + refused.fulfillments.insert("issued_at".to_owned(), action); + assert_declared(block_on(restarted.execute(refused)).unwrap_err()); + assert_eq!(block_on(store.load(&subject)).unwrap(), before); + assert_eq!(block_on(store.lookup_record("refused")).unwrap(), None); + } + assert_eq!(block_on(store.history(&subject)).unwrap().records.len(), 1); +} diff --git a/crates/entity-executor/tests/service_3_fulfillment_retry.rs b/crates/entity-executor/tests/service_3_fulfillment_retry.rs index 9c94f1f9..3a332fef 100644 --- a/crates/entity-executor/tests/service_3_fulfillment_retry.rs +++ b/crates/entity-executor/tests/service_3_fulfillment_retry.rs @@ -2,9 +2,9 @@ use std::collections::BTreeMap; -use entity_core::{OperationFieldAction, Registry}; +use entity_core::{CoreError, OperationFieldAction, Registry}; use entity_executor::{ - test_support::block_on, BatchAction, CreateRequest, ExecuteRequest, Executor, + test_support::block_on, BatchAction, CreateRequest, ExecuteRequest, ExecutionError, Executor, }; use entity_store::{ asynchronous::{ @@ -112,6 +112,137 @@ fn legacy_execute(entity: &str, record_id: &str) -> ExecuteRequest { } } +fn guarded_registry(selector: serde_json::Value, fulfills: serde_json::Value) -> Registry { + let mut registry = Registry::new(); + registry + .register(serde_json::from_value(json!({ + "entity": "invoice", "version": 1, "semantics": "service/3", + "schema": { "fields": { + "issued_at": { "type": "string", "required": true }, + "note": { "type": "string" } + }}, + "lifecycle": { "initial": "Draft", "states": ["Draft"] }, + "operations": { "Issue": { + "arguments": { "fields": { "accept": { "type": "boolean", "required": true } } }, + "outcomes": [ + { "name": "rejected", "when": selector, + "refuses": { "error": "NotAccepted", "message": "Invoice cannot be issued" } }, + { "name": "issued", "effect": "updates", "fulfills": fulfills } + ] + }} + })).expect("guarded definition parses")) + .expect("guarded definition validates"); + registry +} + +fn seed_guarded_invoice(executor: &Executor<'_>) { + block_on(executor.create(CreateRequest { + subject: Subject::new("invoice", "i-1").unwrap(), + definition_version: 1, + fields: json!({"issued_at": "pending", "note": "remove"}), + recording: recording("r-1"), + })) + .expect("creation commits"); +} + +fn assert_refusal_preserves_state(selector: serde_json::Value) { + let registry = guarded_registry( + selector, + json!({ + "issued_at": { "actions": "required" }, "note": { "actions": "optional" } + }), + ); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + seed_guarded_invoice(&executor); + let subject = Subject::new("invoice", "i-1").unwrap(); + let before = block_on(store.load(&subject)).unwrap(); + let history = block_on(store.history(&subject)).unwrap(); + let mut request = execute(fulfill("must not be stored")); + request.arguments = json!({"accept": false}); + let original_request = request.clone(); + let batch_key = BatchKey::Named("refused-batch".to_owned()); + for batched in [false, true] { + let result = if batched { + block_on(executor.batch( + batch_key.clone(), + vec![BatchAction::Execute(request.clone())], + )) + } else { + block_on(executor.execute(request.clone())) + }; + let error = result.expect_err("declared refusal survives supplied success actions"); + assert!( + matches!(error, ExecutionError::Core(CoreError::Refused { + ref outcome, ref error, ref message + }) if outcome == "rejected" && error == "NotAccepted" + && message.as_deref() == Some("Invoice cannot be issued")), + "{error:?}" + ); + assert_eq!(request, original_request); + assert_eq!(block_on(store.load(&subject)).unwrap(), before); + assert_eq!(block_on(store.history(&subject)).unwrap(), history); + assert!(block_on(store.lookup_record("r-2")).unwrap().is_none()); + assert!(block_on(store.lookup_batch(&batch_key)).unwrap().is_none()); + } +} + +#[test] +fn refusal_ignores_success_fulfillments() { + assert_refusal_preserves_state(json!({"eq": ["$args.accept", false]})); +} + +#[test] +fn subject_refusal_ignores_success_fulfillments() { + assert_refusal_preserves_state(json!({"eq": ["$fields.issued_at", "pending"]})); +} + +#[test] +fn accepted_outcomes_still_require_exact_fulfillment_keys() { + for (requirements, actions, missing, extra) in [ + ( + json!({}), + fulfill("issued"), + vec![], + vec!["issued_at", "note"], + ), + ( + json!({"issued_at": {"actions": "required"}}), + BTreeMap::new(), + vec!["issued_at"], + vec![], + ), + ( + json!({"issued_at": {"actions": "required"}}), + fulfill("issued"), + vec![], + vec!["note"], + ), + ] { + let registry = guarded_registry(json!(false), requirements); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + seed_guarded_invoice(&executor); + let subject = Subject::new("invoice", "i-1").unwrap(); + let before = block_on(store.load(&subject)).unwrap(); + let history = block_on(store.history(&subject)).unwrap(); + let mut request = execute(actions); + request.arguments = json!({"accept": true}); + let error = + block_on(executor.execute(request)).expect_err("success contract remains exact"); + assert!( + matches!(error, ExecutionError::Core(CoreError::FulfillmentKeysMismatch { + ref operation, ref outcome, missing: ref actual_missing, extra: ref actual_extra + }) if operation == "Issue" && outcome == "issued" + && actual_missing == &missing && actual_extra == &extra), + "{error:?}" + ); + assert_eq!(block_on(store.load(&subject)).unwrap(), before); + assert_eq!(block_on(store.history(&subject)).unwrap(), history); + assert!(block_on(store.lookup_record("r-2")).unwrap().is_none()); + } +} + #[test] fn retry_restart_and_verified_history_preserve_set_and_removal_actions() { let registry = registry(); diff --git a/crates/entity-executor/tests/version_binding_review.rs b/crates/entity-executor/tests/version_binding_review.rs new file mode 100644 index 00000000..8f316ab8 --- /dev/null +++ b/crates/entity-executor/tests/version_binding_review.rs @@ -0,0 +1,184 @@ +//! Adversarial exact-version selection and byte-compatibility checks. + +use std::collections::BTreeMap; + +use entity_core::{CoreError, Registry}; +use entity_executor::{ + test_support::block_on, CreateRequest, ExecuteRequest, ExecutionError, Executor, + VersionedBatchAction, +}; +use entity_store::{ + asynchronous::{ + AsyncRecordedReader, AsyncStateReader, AsyncStoreError, BatchKey, MemoryRecordedStore, + RecordLookup, Subject, + }, + Recording, +}; +use serde_json::json; + +fn registry() -> Registry { + let mut registry = Registry::new(); + for version in [1, 2] { + registry + .register( + serde_json::from_value(json!({ + "entity": "invoice", "version": version, "semantics": "service/3", + "schema": {"fields": {}}, + "lifecycle": {"initial": "Open", "states": ["Open"]}, + "operations": {"Pay": { + "arguments": {"fields": {"accept": {"type": "boolean", "required": true}}}, + "outcomes": [ + {"name": format!("rejected-v{version}"), + "when": {"eq": ["$args.accept", version == 2]}, + "refuses": {"error": format!("RejectedV{version}")}}, + {"name": "paid", "effect": "updates"} + ] + }} + })) + .unwrap(), + ) + .unwrap(); + } + registry +} + +fn recording(id: &str) -> Recording { + Recording { + record_id: id.into(), + recorded_at: "2026-10-03T00:00:00Z".into(), + correlation: None, + causation: None, + actor: None, + } +} + +fn create(version: u32) -> CreateRequest { + CreateRequest { + subject: Subject::new("invoice", "a").unwrap(), + definition_version: version, + fields: json!({}), + recording: recording("created"), + } +} + +fn execute(accept: bool) -> ExecuteRequest { + ExecuteRequest { + subject: Subject::new("invoice", "a").unwrap(), + expected_revision: 1, + operation: "Pay".into(), + arguments: json!({"accept": accept}), + fulfillments: BTreeMap::new(), + recording: recording("paid"), + } +} + +#[test] +fn divergent_registered_versions_select_only_the_requested_guard() { + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + for (version, accept) in [(1, false), (2, true)] { + store.clear_trace(); + let result = block_on(executor.execute_versioned(execute(accept), version)); + assert!(matches!(result, + Err(ExecutionError::Core(CoreError::Refused {outcome, error, message})) + if outcome == format!("rejected-v{version}") + && error == format!("RejectedV{version}") && message.is_none())); + assert_eq!(store.trace(), vec!["lookup_batch", "lookup_record:paid"]); + } + store.clear_trace(); + assert!(matches!( + block_on(executor.execute_versioned(execute(false), 3)), + Err(ExecutionError::Core(CoreError::EntityNotRegistered {entity, version: 3})) + if entity == "invoice" + )); + assert_eq!(store.trace(), vec!["lookup_batch", "lookup_record:paid"]); +} + +#[test] +fn explicit_and_row_bound_execution_persist_identical_bytes_and_replay_across_apis() { + for version in [1, 2] { + let registry = registry(); + let legacy = MemoryRecordedStore::new(); + let explicit = MemoryRecordedStore::new(); + for (store, versioned) in [(&legacy, false), (&explicit, true)] { + let executor = Executor::new(®istry, store); + block_on(executor.create(create(version))).unwrap(); + let request = execute(version == 1); + let committed = if versioned { + block_on(executor.execute_versioned(request.clone(), version)).unwrap() + } else { + block_on(executor.execute(request.clone())).unwrap() + }; + let empty = Registry::new(); + let restarted = Executor::new(&empty, store); + let retry = if versioned { + block_on(restarted.execute(request.clone())).unwrap() + } else { + block_on(restarted.execute_versioned(request.clone(), version)).unwrap() + }; + assert!(retry.replayed()); + assert_eq!(retry.receipt(), committed.receipt()); + assert!(matches!( + block_on(restarted.execute_versioned(request, 3 - version)), + Err(ExecutionError::Store(AsyncStoreError::RecordConflict {record_id})) + if record_id == "paid" + )); + } + let Some(RecordLookup::Committed(legacy_record)) = + block_on(legacy.lookup_record("paid")).unwrap() + else { + panic!("legacy execution did not commit"); + }; + let Some(RecordLookup::Committed(explicit_record)) = + block_on(explicit.lookup_record("paid")).unwrap() + else { + panic!("explicit execution did not commit"); + }; + assert_eq!(legacy_record.request_bytes, explicit_record.request_bytes); + assert_eq!(legacy_record.entry, explicit_record.entry); + assert_eq!(legacy_record.receipt, explicit_record.receipt); + } +} + +#[test] +fn accepted_wrong_version_in_local_batch_rolls_back_and_releases_all_identities() { + let registry = registry(); + let store = MemoryRecordedStore::new(); + let executor = Executor::new(®istry, &store); + let key = BatchKey::Named("mixed-versions".into()); + let actions = |version| { + vec![ + VersionedBatchAction::Create(create(2)), + VersionedBatchAction::Execute { + definition_version: version, + request: execute(version == 1), + }, + ] + }; + assert!(matches!( + block_on(executor.batch_versioned(key.clone(), actions(1))), + Err(ExecutionError::Core(CoreError::EntityMismatch { + expected_version: 1, + actual_version: 2, + .. + })) + )); + assert_eq!(block_on(store.lookup_record("created")).unwrap(), None); + assert_eq!(block_on(store.lookup_record("paid")).unwrap(), None); + assert_eq!(block_on(store.lookup_batch(&key)).unwrap(), None); + assert_eq!( + block_on(store.load(&Subject::new("invoice", "a").unwrap())).unwrap(), + None + ); + let committed = block_on(executor.batch_versioned(key.clone(), actions(2))).unwrap(); + assert!(!committed.replayed()); + let state = block_on(store.load(&Subject::new("invoice", "a").unwrap())) + .unwrap() + .unwrap(); + assert_eq!((state.version, state.revision), (2, 2)); + let empty = Registry::new(); + let replay = block_on(Executor::new(&empty, &store).batch_versioned(key, actions(2))).unwrap(); + assert!(replay.replayed()); + assert_eq!(replay.receipt(), committed.receipt()); +} diff --git a/crates/entity-graph/Cargo.toml b/crates/entity-graph/Cargo.toml index d905a49a..14199bf6 100644 --- a/crates/entity-graph/Cargo.toml +++ b/crates/entity-graph/Cargo.toml @@ -11,7 +11,7 @@ rust-version.workspace = true # could reach a filesystem or a clock is one that could draw something the definition does not say, # and the drawing would stop being reproducible from the definition alone. [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } +entity-core = { path = "../entity-core", version = "0.26.0" } [dev-dependencies] serde_json.workspace = true diff --git a/crates/entity-mcp/Cargo.toml b/crates/entity-mcp/Cargo.toml index 51595d67..1ee2fd06 100644 --- a/crates/entity-mcp/Cargo.toml +++ b/crates/entity-mcp/Cargo.toml @@ -8,10 +8,10 @@ repository.workspace = true rust-version.workspace = true [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } -entity-shell = { path = "../entity-shell", version = "0.25.1" } -entity-store = { path = "../entity-store", version = "0.25.1" } -entity-surface = { path = "../entity-surface", version = "0.25.1" } +entity-core = { path = "../entity-core", version = "0.26.0" } +entity-shell = { path = "../entity-shell", version = "0.26.0" } +entity-store = { path = "../entity-store", version = "0.26.0" } +entity-surface = { path = "../entity-surface", version = "0.26.0" } serde_json.workspace = true [lints] diff --git a/crates/entity-postgres/Cargo.toml b/crates/entity-postgres/Cargo.toml index 36110159..0f7f7b0b 100644 --- a/crates/entity-postgres/Cargo.toml +++ b/crates/entity-postgres/Cargo.toml @@ -22,13 +22,13 @@ eventlog-facade = [ ] [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } -entity-eventlog = { path = "../entity-eventlog", version = "0.25.1", features = ["sync-bridge", "postgres"], optional = true } -entity-executor = { path = "../entity-executor", version = "0.25.1", optional = true } -entity-store = { path = "../entity-store", version = "0.25.1" } -entity-query = { path = "../entity-query", version = "0.25.1" } -eventlog-core = { git = "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/beyond10x/eventlog", rev = "0a0484634e8c640be29d6b6541d6cc1c1aaef7d3", optional = true } -eventlog-postgres = { git = "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/beyond10x/eventlog", rev = "0a0484634e8c640be29d6b6541d6cc1c1aaef7d3", optional = true } +entity-core = { path = "../entity-core", version = "0.26.0" } +entity-eventlog = { path = "../entity-eventlog", version = "0.26.0", features = ["sync-bridge", "postgres"], optional = true } +entity-executor = { path = "../entity-executor", version = "0.26.0", optional = true } +entity-store = { path = "../entity-store", version = "0.26.0" } +entity-query = { path = "../entity-query", version = "0.26.0" } +eventlog-core = { git = "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/beyond10x/eventlog", rev = "6983cc25eb92e07844b3a6fa3e0decbdb300f43c", optional = true } +eventlog-postgres = { git = "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/beyond10x/eventlog", rev = "6983cc25eb92e07844b3a6fa3e0decbdb300f43c", optional = true } serde.workspace = true serde_json.workspace = true # The fifth direct third-party crate in this workspace, and the first that talks to a server. The diff --git a/crates/entity-query/Cargo.toml b/crates/entity-query/Cargo.toml index f9b49f22..321477c7 100644 --- a/crates/entity-query/Cargo.toml +++ b/crates/entity-query/Cargo.toml @@ -8,8 +8,8 @@ repository.workspace = true rust-version.workspace = true [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } -entity-store = { path = "../entity-store", version = "0.25.1" } +entity-core = { path = "../entity-core", version = "0.26.0" } +entity-store = { path = "../entity-store", version = "0.26.0" } serde.workspace = true serde_json.workspace = true diff --git a/crates/entity-remote/Cargo.toml b/crates/entity-remote/Cargo.toml index e1832905..288040d5 100644 --- a/crates/entity-remote/Cargo.toml +++ b/crates/entity-remote/Cargo.toml @@ -8,8 +8,8 @@ license.workspace = true repository.workspace = true [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } -entity-store = { path = "../entity-store", version = "0.25.1" } +entity-core = { path = "../entity-core", version = "0.26.0" } +entity-store = { path = "../entity-store", version = "0.26.0" } serde.workspace = true serde_json.workspace = true diff --git a/crates/entity-shell/Cargo.toml b/crates/entity-shell/Cargo.toml index 6fadadf1..b6744a3f 100644 --- a/crates/entity-shell/Cargo.toml +++ b/crates/entity-shell/Cargo.toml @@ -8,8 +8,8 @@ repository.workspace = true rust-version.workspace = true [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } -entity-store = { path = "../entity-store", version = "0.25.1" } +entity-core = { path = "../entity-core", version = "0.26.0" } +entity-store = { path = "../entity-store", version = "0.26.0" } serde_json.workspace = true [lints] diff --git a/crates/entity-sqlite/Cargo.toml b/crates/entity-sqlite/Cargo.toml index fb01ed99..0000af13 100644 --- a/crates/entity-sqlite/Cargo.toml +++ b/crates/entity-sqlite/Cargo.toml @@ -12,11 +12,11 @@ default = [] eventlog-facade = ["dep:entity-eventlog", "dep:entity-query", "dep:eventlog-core", "dep:time"] [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } -entity-eventlog = { path = "../entity-eventlog", version = "0.25.1", features = ["sync-bridge", "sqlite"], optional = true } -entity-query = { path = "../entity-query", version = "0.25.1", optional = true } -entity-store = { path = "../entity-store", version = "0.25.1" } -eventlog-core = { git = "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/beyond10x/eventlog", rev = "0a0484634e8c640be29d6b6541d6cc1c1aaef7d3", optional = true } +entity-core = { path = "../entity-core", version = "0.26.0" } +entity-eventlog = { path = "../entity-eventlog", version = "0.26.0", features = ["sync-bridge", "sqlite"], optional = true } +entity-query = { path = "../entity-query", version = "0.26.0", optional = true } +entity-store = { path = "../entity-store", version = "0.26.0" } +eventlog-core = { git = "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/beyond10x/eventlog", rev = "6983cc25eb92e07844b3a6fa3e0decbdb300f43c", optional = true } serde.workspace = true serde_json.workspace = true time = { version = "0.3", optional = true } diff --git a/crates/entity-store/Cargo.toml b/crates/entity-store/Cargo.toml index 58a9535f..da380cd3 100644 --- a/crates/entity-store/Cargo.toml +++ b/crates/entity-store/Cargo.toml @@ -8,7 +8,7 @@ license.workspace = true repository.workspace = true [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } +entity-core = { path = "../entity-core", version = "0.26.0" } serde.workspace = true serde_json.workspace = true # Cross-process advisory locks release automatically on process exit; supports the MSRV and Windows. diff --git a/crates/entity-surface/Cargo.toml b/crates/entity-surface/Cargo.toml index b2592a13..753a0cd1 100644 --- a/crates/entity-surface/Cargo.toml +++ b/crates/entity-surface/Cargo.toml @@ -8,8 +8,8 @@ repository.workspace = true rust-version.workspace = true [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } -entity-graph = { path = "../entity-graph", version = "0.25.1" } +entity-core = { path = "../entity-core", version = "0.26.0" } +entity-graph = { path = "../entity-graph", version = "0.26.0" } serde_json.workspace = true serde_yaml_ng.workspace = true diff --git a/crates/entity-yaml/Cargo.toml b/crates/entity-yaml/Cargo.toml index 40600ad0..8c4ce37e 100644 --- a/crates/entity-yaml/Cargo.toml +++ b/crates/entity-yaml/Cargo.toml @@ -8,7 +8,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] -entity-core = { path = "../entity-core", version = "0.25.1" } +entity-core = { path = "../entity-core", version = "0.26.0" } serde.workspace = true serde_yaml_ng.workspace = true diff --git a/docs/design/eventlog-recorded-adapter-v0.1.md b/docs/design/eventlog-recorded-adapter-v0.1.md index d7f8dac7..0010ec51 100644 --- a/docs/design/eventlog-recorded-adapter-v0.1.md +++ b/docs/design/eventlog-recorded-adapter-v0.1.md @@ -381,3 +381,41 @@ Select exact accepted Eventlog pins after those dependencies qualify; runtime fe bridge companion. Add old/new-reader fixtures before parser extensions and independently review the complete contract before implementation. These proposals close no provider, adapter, facade or migration outcome, and no generic in-memory test replaces actual three-provider qualification. + +## Explicit provider-tracked verification (R-151) + +`CapturePolicy::FullVerification` remains the default of existing constructors. +`EventlogRecordedStore::open_with_policy(backend, authority, limits, policy)` additionally admits +`ProviderTracked`. Both policies fully verify the native capture on open. The tracked policy +subsequently accepts only provider-owned proof of an unchanged observation or a complete, +contiguous acknowledged append suffix. It never treats an unchanged event head as that proof. + +The optional Eventlog `capture_tenant_since` capability binds an opaque, nonserializable checkpoint +to the provider instance, tenant generation, ordered projection specifications and complete-capture +limits. Unsupported providers, foreign or expired checkpoints, lost journal continuity and +unaccounted mutations return a complete capture. SQLite samples external commits, its own changes +and schema changes under its connection mutex and native transaction boundary. The operator +accepted this SQL-visible integrity boundary: writes through other SQLite connections invalidate +warm reuse, including altered old blob bytes with an unchanged event head. Raw database-file edits +that bypass SQLite are outside the warm guarantee; a newly opened handle still verifies content. + +The runtime retains an already verified model. An unchanged proof permits direct reads from it. +A linear recorded-entry suffix verifies every new complete batch, exact physical positions, +global record/batch identities, predecessor replay and all affected projection rows, including +required rows omitted from the supplied delta. Before-values must match the verified base and +final rows must match the authoritative events. Only then may the cache checkpoint advance. +Imports, lineage and unsupported changes use complete verification. A reader holding the old model +keeps that immutable observation; a concurrent advance may rebuild instead of copying the model. +Failed tentative advancement discards the candidate and asks the complete verifier for its result. + +Fixed-size warm batches avoid replaying, sorting or cloning the old shared-clock history. Full +open remains proportional to store content, and requesting a complete snapshot or history remains +proportional to returned data. The release probe `shared_clock_cost` separately measures open, +fixed-size batch, scoped history, batch lookup and snapshot costs at 55, 601 and 1,203 events. +The 2x assertion applies to the warm fixed-size shared-clock batch. It is not a constant-time +promise for reopening a growing store or returning its whole history. + +The separate `ess/provider-tracking` contract executes real SQLite/facade calls and unchanged-head +SQL tampering controls. The original five-library ESS composition is unchanged. Runtime +regressions additionally compare incremental and full models, inject omitted/forged suffix +coordinates and verify that unchanged reads and own appends do not re-enter complete capture. diff --git a/docs/design/eventlog-recorded-sync-bridge-v0.1.md b/docs/design/eventlog-recorded-sync-bridge-v0.1.md index bf1e0268..276cdef1 100644 --- a/docs/design/eventlog-recorded-sync-bridge-v0.1.md +++ b/docs/design/eventlog-recorded-sync-bridge-v0.1.md @@ -490,3 +490,22 @@ the bridge mechanics, then run provider-backed cases where the adapter dependenc These tests close only runtime isolation, bounded admission, response truthfulness and ownership. They do not prove native capture, inline rebuild, canonical decoding/indexes, provider transaction semantics, final dependency pins or legacy import. + +## Explicit tracked reads and scoped facade calls + +`RecordedProviderFacade::start_with_read_policy(registry, owner, config, policy)` and the bridge's +matching constructor select the read policy documented in the recorded-adapter design. Existing +`start` constructors retain `FullVerification`; `BridgeConfig` keeps its source-compatible shape. +`ProviderTracked` is explicit and still performs complete verification during startup. + +`facade.scoped()` exposes `load_recorded`, `read_history`, `read_histories`, `lookup_record` and +`lookup_batch` with the ordinary per-call wait. `read_histories(&subjects, wait)` submits one worker +request and one read scope, returning histories in input order, preserving duplicate positions +and representing absent subjects with empty genesis histories. An empty input returns an empty +list. Reads retain the bridge's typed rejection, dispatch and provider errors; the caller supplies +no arbitrary closure or runtime. Existing complete snapshot APIs remain available. + +The scoped methods use the adapter's existing verified scope for full-verification providers and +the fresh provider-attested model for tracked handles. They do not make a projection row alone +an authority or weaken whole-batch verification. Consumers opt into the tracked constructor and +scoped calls explicitly; no dependency update silently selects the narrower raw-file boundary. diff --git a/docs/design/recorded-execution-v0.1.md b/docs/design/recorded-execution-v0.1.md index ac5cfad1..912f5929 100644 --- a/docs/design/recorded-execution-v0.1.md +++ b/docs/design/recorded-execution-v0.1.md @@ -238,3 +238,30 @@ Later bounded owner units are Eventlog recorded adapter, explicit sync bridge, S and PostgreSQL facade/import. They consume this full contract and preserve command sessions, indexed queries, identity locks and sequence semantics. They are not considered implemented by the reference store. AEP owns the one planning-store migration story and the six actual cutovers. + +## Input refusals before subject existence + +Creation already carries an exact definition version. For a fresh request, a declared creation +refusal is selected before loading or checking the subject. Other creation errors retain their +existing ordering relative to an existing-subject conflict. + +Execution that must select an input refusal before loading uses an explicit caller-selected +definition version. The additive `execute_versioned` and `batch_versioned` entry points carry +that version with execute and merge actions. Existing request structs and batch actions retain +their shapes; legacy execution continues to obtain its version from the loaded row. No entry +point silently chooses the latest or only registered definition. + +Shape validation and verified identity recovery still precede a fresh decision. A fresh +versioned action runs the kernel's `decide_before_load` before that action's subject or merge +history read. A declared input refusal wins over existence, revision and merge checks. A result +requiring loaded state retains those checks and must match the selected definition. Batch members +are decided in order, and no append occurs until all members succeed. + +An explicit version on retry must match the saved decision's version, including imported +evidence. Exact retries continue to use the saved definition after registry removal; a changed +version is a record conflict. The canonical `er.request/*` formats are unchanged. Refusal +recording includes the explicit caller version so its request retains the selected authority. + +A selected declared refusal also precedes validation of success-outcome fulfillment keys. +Successful outcomes continue to require their exact fulfillment contract. This applies at the +executor boundary as well as the kernel boundary. diff --git a/docs/ess/README.md b/docs/ess/README.md index a10b115c..acdb85f9 100644 --- a/docs/ess/README.md +++ b/docs/ess/README.md @@ -23,6 +23,14 @@ remain available. The [service-binding inventory](../design/models/service-bindi is diagnostic material: inventory membership alone is not an executable behavioral assertion. The corresponding requirements are traced to actual scenarios in the core register. +The separate [`provider-tracking` contract](../../ess/provider-tracking/ess-inputs.yaml) covers +the explicit SQLite read-verification policy and scoped facade calls. `task provider-ess-check` +uses the same checker with its `eventlog` feature and Rust 1.91 to execute actual SQLite/facade +operations, including mutations from a second SQL connection. It keeps its own generated suite, +coverage baseline and reports. This is a bounded provider contract; it does not claim coverage +of all Eventlog providers. `task check` runs both contracts. The original five-library checker +retains its Rust 1.85 build without that feature. + ## Calls, values and observations [`domains/core.yaml`](../../ess/domains/core.yaml) owns `entity.core.JsonDocument` and the nominal diff --git a/docs/ess/evidence/provider-tracking/README.md b/docs/ess/evidence/provider-tracking/README.md new file mode 100644 index 00000000..1c558ef4 --- /dev/null +++ b/docs/ess/evidence/provider-tracking/README.md @@ -0,0 +1,34 @@ +# Provider tracking verification + +The final integration gate, `task check`, exited 0 with the published Eventlog dependency +`6983cc25eb92e07844b3a6fa3e0decbdb300f43c`. Both real PostgreSQL lanes executed against the +same disposable fixture. All 421 original ESS scenarios and all 17 provider scenarios passed, +with no ESS skips or unsupported cases. The provider suite contains seven authored semantic +cases and ten generated command-response cases. Exact reports and suites are admitted in AEP. + +The release performance probe was then run explicitly with Rust 1.91: + +```console +CARGO_BUILD_JOBS=2 cargo +1.91.0 test -p entity-eventlog --features sqlite,sync-bridge --release --locked --test shared_clock_cost -- --ignored --nocapture +``` + +It passed on the first final integrated run: warm batch medians were 11.532148 ms, 13.049370 ms, +and 13.568977 ms for 55, 601 and 1,203 initial provider events. Growth was 1.132x and 1.177x, +below the unchanged 2x bound. The ordinary gate intentionally ignores this timing probe; +this separate command executed it. `performance-final.txt` retains the complete test stdout, +starting at `running 1 test`; compiler output with local paths remains private. + +The earlier matched baseline failed (92.517484 ms / 7,038.153041 ms / 12,449.723852 ms). +The first development treatment passed (10.511861 ms / 11.983571 ms / 17.701283 ms). +The final run above follows receipt-validation and projection-alias review corrections and +uses the published Git dependency without path overrides. The governed story and review +records distinguish these runs and retain the negative controls. + +Only warm batches have the 2x claim. Cold full verification and full-history output scale +with their inputs and outputs. SQL-visible changes invalidate tracked observations; +raw database-file edits bypassing SQLite are outside this explicitly selected warm policy. +No consumer adoption or complete consumer-invocation benchmark is claimed. + +`source.sha256` identifies the implementation and Cargo inputs tested. Verify it from the +repository root with `sha256sum --check`. The final commit additionally records documents +and evidence without changing those inputs. diff --git a/docs/ess/evidence/provider-tracking/performance-final.txt b/docs/ess/evidence/provider-tracking/performance-final.txt new file mode 100644 index 00000000..63cf80b3 --- /dev/null +++ b/docs/ess/evidence/provider-tracking/performance-final.txt @@ -0,0 +1,34 @@ +running 1 test +seed events=55 recorded_entries=54 groups=27 blob_bytes=2846892 +unchanged events=55 elapsed=2.474634ms calls_before=StoreCalls { captures: 1, model_builds: 1, model_advances: 0, records_decoded: 54, scoped_reads: 0 } calls_after=StoreCalls { captures: 1, model_builds: 1, model_advances: 0, records_decoded: 54, scoped_reads: 0 } +seed_and_verify events=55 elapsed=585.043127ms +sample events=55 round=0 batch=14.556882ms history=1.191689ms lookup=158.489µs snapshot=2.509212ms +sample events=55 round=1 batch=11.639524ms history=627.525µs lookup=114.908µs snapshot=1.594789ms +sample events=55 round=2 batch=11.582753ms history=641.615µs lookup=94.285µs snapshot=1.936809ms +sample events=55 round=3 batch=10.049362ms history=550.86µs lookup=40.806µs snapshot=1.67534ms +sample events=55 round=4 batch=11.532148ms history=641.767µs lookup=44.298µs snapshot=1.878407ms +sample events=55 round=5 batch=10.854834ms history=536.837µs lookup=60.607µs snapshot=1.176457ms +median initial_events=55 open=93.944214ms batch=11.532148ms history=627.525µs lookup=60.607µs snapshot=1.67534ms +seed events=601 recorded_entries=600 groups=300 blob_bytes=31641002 +unchanged events=601 elapsed=27.588126ms calls_before=StoreCalls { captures: 1, model_builds: 1, model_advances: 0, records_decoded: 600, scoped_reads: 0 } calls_after=StoreCalls { captures: 1, model_builds: 1, model_advances: 0, records_decoded: 600, scoped_reads: 0 } +seed_and_verify events=601 elapsed=6.338161219s +sample events=601 round=0 batch=17.232166ms history=10.988649ms lookup=150.966µs snapshot=28.851549ms +sample events=601 round=1 batch=11.674245ms history=5.560104ms lookup=95.065µs snapshot=17.265898ms +sample events=601 round=2 batch=14.411778ms history=7.701056ms lookup=126.041µs snapshot=17.606226ms +sample events=601 round=3 batch=11.938936ms history=5.620425ms lookup=182.614µs snapshot=13.085607ms +sample events=601 round=4 batch=13.239059ms history=6.838991ms lookup=95.633µs snapshot=11.202798ms +sample events=601 round=5 batch=13.04937ms history=5.635716ms lookup=152.568µs snapshot=11.701499ms +median initial_events=601 open=981.221356ms batch=13.04937ms history=5.635716ms lookup=126.041µs snapshot=13.085607ms +seed events=1203 recorded_entries=1202 groups=601 blob_bytes=63391084 +unchanged events=1203 elapsed=55.311302ms calls_before=StoreCalls { captures: 1, model_builds: 1, model_advances: 0, records_decoded: 1202, scoped_reads: 0 } calls_after=StoreCalls { captures: 1, model_builds: 1, model_advances: 0, records_decoded: 1202, scoped_reads: 0 } +seed_and_verify events=1203 elapsed=13.099031919s +sample events=1203 round=0 batch=15.522732ms history=21.529834ms lookup=177.619µs snapshot=55.759246ms +sample events=1203 round=1 batch=16.485452ms history=11.702935ms lookup=124.087µs snapshot=38.87472ms +sample events=1203 round=2 batch=13.519375ms history=10.451956ms lookup=94.512µs snapshot=35.866736ms +sample events=1203 round=3 batch=13.568977ms history=9.808887ms lookup=184.847µs snapshot=35.543147ms +sample events=1203 round=4 batch=14.32953ms history=9.99339ms lookup=104.844µs snapshot=34.838364ms +sample events=1203 round=5 batch=11.088883ms history=10.945009ms lookup=194.498µs snapshot=21.465093ms +median initial_events=1203 open=1.854794335s batch=13.568977ms history=10.451956ms lookup=124.087µs snapshot=35.543147ms +test warm_shared_clock_batches_remain_within_twice_small_store_cost ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 24.02s diff --git a/docs/ess/evidence/provider-tracking/source.sha256 b/docs/ess/evidence/provider-tracking/source.sha256 new file mode 100644 index 00000000..ebe1a2f4 --- /dev/null +++ b/docs/ess/evidence/provider-tracking/source.sha256 @@ -0,0 +1,10 @@ +11c0b737ad6fe6612faa664a22c9e1721726d8ab6d0734ed59a0a135e8f23267 crates/entity-eventlog/tests/shared_clock_cost.rs +895173eeef5b5e5b129c85f856e7c07842cccc79b140dd4005c229b731abea9e crates/entity-eventlog/src/adapter.rs +a60a2a6ad8399d749fa1c808a8ce4f117a265f3533a897d955a534e27d0d83bf crates/entity-eventlog/src/adapter/tracked.rs +bebdcb407b70329f77d561bac64c95ff438f3b07adb0cc4b5623ab640c81c7d3 crates/entity-eventlog/src/adapter/scoped.rs +b2a3de5266760dfdf61289183422d71bb182d4c0cb3be8dc047639df7bf26f6b crates/entity-eventlog/src/facade.rs +a7a6da41fc1888a52bee340aeb936c36aa87e902531425cdfb4c822d0cb4d320 crates/entity-eventlog/src/sync.rs +fa44885146c07d707679c5fee6b909651d20541270f575c7c003f876dbb4fba1 Cargo.toml +595a7d0fe1fc745dd7ffe091de2e549832701b043d3a41824dc312c6ae3b3319 Cargo.lock +eacc89cff2909b1f12b03996af8fec8f98f8d194952951c9402ab8461ce7a25b checks/ess-conformance/Cargo.toml +345068d8f5877ae6ebed62ceb0589b9459f066ab4f9870fce284f88f7dd9a0c9 checks/ess-conformance/Cargo.lock diff --git a/docs/ess/executor-traceability.md b/docs/ess/executor-traceability.md index 36b53c1e..8e606d5e 100644 --- a/docs/ess/executor-traceability.md +++ b/docs/ess/executor-traceability.md @@ -49,6 +49,8 @@ has no lineage capability. The store domain separately executes the pure lineage | R-104 and R-122 | Supplied reader refusal propagates through the real executor | `unreachable-is-never-absent` | | R-126 | Exact imported matching and named unverifiability | `imported-exact-retry-is-historical`, `historical-observation-has-no-committed-receipt`, `imported-missing-definition-cannot-prove-retry` | | R-157, R-159 | `Executor::recording_refusals`, actual `RecordedRefusal` arguments | `optional-refusal-recorder-sees-conflicts-not-malformed-input`, `ordinary-forked-write-records-refusal`, `kernel-refusal-is-recorded` | +| Declared refusal precedes success fulfillment validation | `Executor::execute`, completed input and stored-field guard refusals preserve their outcome, error and message without state/history/receipt changes | `refusal-ignores-success-fulfillments`, `subject-refusal-ignores-success-fulfillments` | +| R-149, R-122, R-123 | `Executor::create` selects declared refusals before existence; `Executor::execute_versioned` and `Executor::batch_versioned` bind explicit definition authority before loading, preserve exact historical retries and append atomically | `executor-input-refusal-existing-create`, `executor-input-refusal-missing-execute`, `issue-50-batch-refusal-is-atomic`, `issue-50-malformed-version`, `issue-50-versioned-merge-preserves-proof` | | R-156–R-158, executor preparation portion | `merge_base` verifies heads, selected first state and highest revision; `branch_tips` determines append parents | `merge-checks-head-and-highest-revision`, `merge-candidate-uses-first-state-and-all-tips`, `merge-appends-over-observation-tips`, `empty-history-is-not-a-merge` | The complete R-158 durable fork/merge contract additionally requires a lineage-capable provider; diff --git a/docs/requirements.md b/docs/requirements.md index 4812bf1c..7d7150e9 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -218,11 +218,11 @@ it serialises to today. | R-147 | ⊕ Ordering two text values is answered inside the declared scales, which are a declaration snapshotted into every record rather than a lookup: by rank where one scale contains both, and `Truth::Unknown` where none does or where two disagree. A definition that declares no scale is in the unknown case for every pair, and never answers `false`. | `a_timestamp_comparison_with_no_declared_scale_is_unknown_and_not_false`, `a_timestamp_comparison_inside_a_declared_scale_answers_by_rank`, `two_declared_scales_that_disagree_about_one_pair_answer_unknown`, `the_declared_scales_are_snapshotted_into_the_record_and_replay_reads_the_snapshot` | | R-148 | ⊕ Four `service/1` operators and two address forms. `compare` is the exact three-valued scalar comparison and answers `Unknown` where the existing operators answer `false`; `truthy` reproduces the source's bare-path reading arm for arm; `for_all`/`for_any` walk an array's elements in index order and a map's values in canonical key order, are vacuous over an empty collection and `Unknown` over an unobserved one, short-circuit, and bind an element that an inner binder may shadow. `.count` and `.` resolve under `service/1` only, a declared map's keys stay unaddressable, and both are checked at registration. A condition nests at most 32 deep, a literal list or mapping `compare` operand is refused, and a quantifier's binder, collection and body scope are all checked where they are written. | `compare_answers_unknown_where_gt_answers_false_for_two_non_numbers`, `compare_over_a_resolved_array_operand_is_unknown`, `truthy_over_text_is_true_unless_empty_or_the_word_false`, `truthy_over_a_number_is_the_zero_test_and_over_nothing_is_unknown`, `for_all_over_an_empty_collection_is_true_and_for_any_is_false`, `for_all_over_an_unobserved_collection_is_unknown_rather_than_vacuously_true`, `a_nested_quantifier_body_reaches_the_outer_element_and_an_inner_binder_shadows_it`, `for_all_over_a_map_walks_its_values_in_canonical_key_order`, `a_quantifier_body_reading_an_address_outside_its_scope_is_refused_at_registration`, `a_condition_nested_past_thirty_two_is_refused_with_its_limit`, `a_nominal_invariant_inside_a_list_lowers_to_a_for_all_over_that_path`, `an_empty_any_of_is_unknown_when_unobserved_and_false_when_observed`, `a_collection_count_resolves_under_service_1_and_resolves_to_nothing_under_kernel_1`, `an_array_ordinal_address_resolves_under_service_1_only` | -| R-149 | ⊕ A host can normalize and partially select an operation before loading its subject. Input-only refusals finish without a load; a state test or unresolved subject fact returns one opaque continuation binding the validated definition, normalized arguments, operation and exact subject. Partial Kleene and quantifier evaluation preserves dominating truths, empty collections, binders and genuine known-input diagnostics; continuation rejoins the direct decision path after entity, identity and state checks. | design (`service-binding-boundary-v0.1.md` § 1), `a_nonpositive_payment_refuses_before_any_subject_load`, `a_positive_payment_requires_the_exact_subject_and_continues_in_er`, `a_preload_scan_stops_at_the_first_subject_dependent_branch`, `implicit_kernel_and_branchless_service_operations_preload_then_match_direct_decide`, `kleene_dominating_guards_refuse_without_loading_an_unknown_subject`, `preload_quantifiers_preserve_empty_collections_and_available_binders`, `unloaded_subject_existence_is_not_confused_with_an_absent_field`, `a_state_guard_is_not_evaluated_before_the_subject_is_loaded`, `an_unknown_input_guard_refuses_without_trying_a_later_default`, `a_prepared_operation_binds_definition_input_operation_and_identity` | +| R-149 | ⊕ A host can normalize and partially select an operation before loading its subject. Input-only refusals finish without a load; a state test or unresolved subject fact returns one opaque continuation binding the validated definition, normalized arguments, operation and exact subject. Partial Kleene and quantifier evaluation preserves dominating truths, empty collections, binders and genuine known-input diagnostics; continuation rejoins the direct decision path after entity, identity and state checks. | design (`service-binding-boundary-v0.1.md` § 1), `a_nonpositive_payment_refuses_before_any_subject_load`, `a_positive_payment_requires_the_exact_subject_and_continues_in_er`, `a_preload_scan_stops_at_the_first_subject_dependent_branch`, `implicit_kernel_and_branchless_service_operations_preload_then_match_direct_decide`, `kleene_dominating_guards_refuse_without_loading_an_unknown_subject`, `preload_quantifiers_preserve_empty_collections_and_available_binders`, `unloaded_subject_existence_is_not_confused_with_an_absent_field`, `a_state_guard_is_not_evaluated_before_the_subject_is_loaded`, `an_unknown_input_guard_refuses_without_trying_a_later_default`, `a_prepared_operation_binds_definition_input_operation_and_identity`, `executor_input_refusal_existing_create`, `executor_input_refusal_missing_execute`, `versioned_refusal_precedes_existing_revision_and_definition_mismatch`, `accepted_input_preserves_absence_revision_and_definition_checks`, `versioned_batch_refusal_rolls_back_earlier_local_decisions`, `explicit_definition_does_not_require_registry_for_committed_retry`, `imported_execution_retry_checks_explicit_version_before_accepting_identity`, `named_mixed_batch_retry_uses_saved_versions_and_rejects_changed_binding`, `versioned_merge_refusal_precedes_the_merge_history_read`, `recorded_refusal_keeps_explicit_definition_authority`, `legacy_execute_keeps_row_authority_without_choosing_a_registered_version`, `legacy_committed_execution_can_be_retried_with_its_explicit_saved_version`, `versioned_empty_and_invalid_shape_requests_perform_no_io` | | R-150 | ⊕ `service/2` adds typed conditional presence from one validated optional, no-default command argument leaf to optional creation state, event payload and response members. Missing omits the member; present copies its exact value; present null remains present and is admitted only when its source type admits it. Records and retries preserve the choice under `er.record/3` and `er.request/3`; `/1`, `/2` and `er.batch/1` remain unchanged and older readers refuse `/3` at the tag. | design (`service-binding-boundary-v0.1.md` §§ 2–3), `conditional_presence_distinguishes_absent_present_and_null_in_all_output_positions`, `one_optional_argument_reuses_one_presence_and_value_across_state_event_and_response`, `conditional_presence_refuses_wrong_types_and_missing_required_neighbors_before_selection`, `service_2_replay_recomputes_presence_and_refuses_every_tampered_position`, `kernel_1_and_service_1_keep_their_definitions_decisions_and_refusal_order`, `service_2_uses_literal_record_3_and_request_3_framing`, `mixed_record_domains_remain_members_of_er_batch_1`, `service_2_retry_distinguishes_absent_and_present_optional_arguments` | -| R-151 | ⊕ `entity-eventlog` preserves Entity Runtime's complete canonical record, request and batch bytes behind domain-separated immutable blobs and single-field Eventlog references; an immutable logical-scope/tenant/generation binding, transaction-local guards and four fixed inline indexes retain global record/batch identity, atomic mixed batches, zero-event decisions, observations, physical receipt membership and imported evidence without invented chronology. Ordinary reads do not initialize storage and refuse missing, substituted, redacted, corrupt or drifted authority. A command's reads — the `EventlogOperationStore` readers, the executor's batch reads and the append's preflight and post-commit check — are per-entity: they read the binding stream, the streams of the subjects the command names, the blobs those events bind and the index rows of its record ids, batch key and subjects, and take no complete tenant capture. A subject an index row names for one of those ids or keys is read too, and so is every subject sharing a batch with a record read. The identity is checked only behind a present binding row, because the Eventlog 0.5.0 port offers no non-minting identity call; a tenant forgotten between those two calls can still be re-minted. What is read is verified as a capture is, but only the index rows for the keys the command names are held to the events: a tampered row of another entity is no longer refused by a command. A per-entity read that does not verify after three attempts falls back to one complete capture, so a concurrent honest writer ends as a revision conflict or success and never as a tamper verdict. A command is refused with `BatchExceedsReadBounds` before any upload when it would leave the tenant holding more events, blobs or index rows than the handle's `CaptureLimits`, counted from the handle's last complete capture plus its own writes since. The store handle's own readers, `complete_snapshot`, recorded refusals, imports and recovery after an uncertain, conflicting or reused-identity reply still use one native complete tenant capture per read. Provisioning, imported anchors, attachment and rebuild are explicit administrative capabilities. A batch of imported boundaries establishes every member in one atomic append group, from one capture and one post-capture, writing byte-identical anchors, blob keys and receipts to the same boundaries imported one at a time; a repeated subject settles as the repeat N singular calls settle, differing anchors for one subject and a record identity two members share are refused from the batch's own capture before any blob is uploaded, the guard takes its keys in key order rather than caller order and is idempotent over a batch-bearing retry of its own commit, a provider that refuses guarded blob-bearing groups gets the same bytes through the per-blob path, a batch that would commit past the capture bounds its own handle reads with refuses before it writes, a blocked batch names the member actually held with the revision it holds, and any refusal leaves none of the batch committed — with no blob of it bound on a provider that binds a group's blobs in the group's transaction. The optional synchronous facade owns its provider on one dedicated runtime thread, bounds admission, distinguishes cancellation before dispatch from write uncertainty after dispatch, contains panics, and reports actual retirement. The adapter/runtime lane requires Rust 1.91 while the pure Entity Runtime workspace remains on Rust 1.85. | design (`eventlog-recorded-adapter-v0.1.md` and its encoding, indexes, errors/import and sync-bridge companions), `literal_reference_vectors_pin_framing_domains_lengths_and_escaping`, `closed_binding_decode_refuses_unknown_fields_and_noncanonical_order`, `a_digest_is_bound_to_its_exact_domain`, `file_provider_provisions_replays_opens_and_rebuilds`, `sqlite_memory_provider_provisions_replays_opens_and_rebuilds`, `sqlite_file_provider_provisions_replays_opens_and_rebuilds`, `postgres_provider_provisions_replays_opens_and_rebuilds_when_assigned`, `synchronous_bridge_owns_reopened_provider_and_reports_retirement`, `unknown_commit_recovery_preserves_a_conclusive_foreign_binding_conflict`, `a_foreign_binding_is_not_a_conflict_until_its_derived_row_is_validated`, `production_worker_startup_owns_and_drops_the_driver_on_its_thread`, `production_worker_calls_are_runtime_independent_and_reentrant_safe`, `production_worker_enforces_capacity_and_discards_cancelled_queue_cells`, `dispatch_deadline_cas_has_one_winner_and_cancelled_work_never_calls_the_driver`, `timed_out_caller_does_not_cancel_commit_and_same_key_retry_recovers_it`, `every_executor_write_keeps_its_original_key_after_dispatch_deadline`, `drain_finishes_in_order_and_escalation_cancels_only_queued_work`, `retirement_timeout_later_joins_once_and_preserves_provider_failure`, `worker_panics_cancel_real_queued_cells_without_stranding_waiters`, `drop_requests_closure_without_waiting_for_an_active_future`, `panicked_or_stopped_worker_keeps_dispatched_write_uncertainty`, `production_worker_preserves_every_driver_error_classification`, `synchronous_bridge_startup_refuses_missing_drifted_and_dirty_projection_admission`, `postgres_bridge_awaits_worker_owned_provider_retirement_when_assigned`, `file_provider_preserves_complete_service_3_composition`, `sqlite_memory_provider_preserves_complete_service_3_composition`, `sqlite_file_provider_preserves_complete_service_3_composition`, `postgres_provider_preserves_complete_service_3_composition_when_assigned`, `sqlite_bridge_crosses_service_3_retry_history_and_reopen`, `a_batch_import_writes_the_same_bytes_as_singular_imports_from_one_capture`, `a_refused_member_leaves_no_part_of_the_batch_committed`, `a_batch_naming_one_subject_twice_with_different_anchors_is_refused_before_any_write`, `an_empty_batch_reaches_no_provider`, `a_guard_refused_batch_binds_no_blob_on_the_sqlite_provider`, `a_batch_refuses_a_record_identity_two_of_its_members_share`, `a_batch_naming_one_subject_twice_settles_as_two_singular_calls_settle`, `the_blob_and_append_mappings_agree_on_the_only_variant_put_blob_documents`, `a_batch_import_commits_through_the_port_default_on_the_sqlite_provider`, `a_batch_bearing_retry_is_admitted_over_the_commit_it_already_made`, `a_partially_overlapping_group_reports_the_member_that_is_actually_occupied`, `a_partially_overlapping_group_commits_no_part_of_itself`, `a_batch_commits_no_more_than_its_own_handle_can_read_back`, `the_port_default_fails_closed_and_binds_no_blob`, `the_sqlite_override_commits_a_group_with_its_blob_and_a_refused_guard_binds_neither`, `the_guard_refuses_a_record_identity_another_writer_committed_after_the_pre_capture`, `the_guard_refuses_a_subject_another_writer_answered_for_under_different_bytes`, `one_anchor_naming_a_record_identity_twice_is_refused`, `a_command_on_one_subject_reads_only_its_own_stream_on_the_tree_provider`, `a_command_on_one_subject_reads_only_its_own_stream_on_the_sqlite_provider`, `a_command_on_one_subject_reads_only_its_own_stream_on_the_file_provider`, `a_command_refuses_a_substituted_stream_identity_on_the_tree_provider`, `a_command_refuses_a_substituted_stream_identity_on_the_sqlite_provider`, `a_command_refuses_a_forged_index_row_of_its_own_subject_on_the_tree_provider`, `a_command_refuses_a_forged_index_row_of_its_own_subject_on_the_sqlite_provider`, `r151_a_command_read_is_per_entity_and_a_handle_read_is_one_complete_capture`, `an_honest_concurrent_writer_is_a_revision_conflict_not_provider_integrity`, `a_handle_does_not_commit_commands_it_can_no_longer_read_back`, `a_read_through_a_forgotten_tenant_does_not_mint_it_a_new_identity` | -| R-152 | `service/3` admits a closed, schema-checked operation fulfillment map with Set, Preserve and optional Remove. An opaque continuation requests actions only after exact subject, ordered outcome selection, refusal, state and preconditions pass; one resulting map drives schema, identity, invariants, events and response. Invalid placements and action coordinates are typed refusals. | design (`service-operation-field-fulfillment-v0.1.md`), `set_preserve_and_remove_apply_once_and_share_the_result_with_event_response_and_replay`, `preserve_keeps_loaded_presence_while_every_wrong_action_coordinate_is_typed`, `selection_refusal_preconditions_and_exact_subject_finish_before_any_fulfillment`, `registration_accumulates_every_fulfillment_placement_and_presence_defect`, `invariants_judge_the_post_action_map_and_event_only_service_3_history_is_refused` | +| R-151 | ⊕ `entity-eventlog` preserves Entity Runtime's complete canonical record, request and batch bytes behind domain-separated immutable blobs and single-field Eventlog references; an immutable logical-scope/tenant/generation binding, transaction-local guards and four fixed inline indexes retain global record/batch identity, atomic mixed batches, zero-event decisions, observations, physical receipt membership and imported evidence without invented chronology. Ordinary reads do not initialize storage and refuse missing, substituted, redacted, corrupt or drifted authority. Under the default `CapturePolicy::FullVerification`, a command's reads — the `EventlogOperationStore` readers, the executor's batch reads and the append's preflight and post-commit check — are per-entity: they read the binding stream, the streams of the subjects the command names, the blobs those events bind and the index rows of its record ids, batch key and subjects, and take no complete tenant capture. A subject an index row names for one of those ids or keys is read too, and so is every subject sharing a batch with a record read. The identity is checked only behind a present binding row, because the Eventlog 0.5.0 port offers no non-minting identity call; a tenant forgotten between those two calls can still be re-minted. What is read is verified as a capture is, but only the index rows for the keys the command names are held to the events: a tampered row of another entity is no longer refused by a command. A per-entity read that does not verify after three attempts falls back to one complete capture, so a concurrent honest writer ends as a revision conflict or success and never as a tamper verdict. A command is refused with `BatchExceedsReadBounds` before any upload when it would leave the tenant holding more events, blobs or index rows than the handle's `CaptureLimits`, counted from the handle's last complete capture plus its own writes since. Under that default policy the store handle's own readers, `complete_snapshot`, recorded refusals, imports and recovery after an uncertain, conflicting or reused-identity reply still use one native complete tenant capture per read. The opt-in `ProviderTracked` policy fully verifies on open, then reuses verified state only under a provider-issued unchanged checkpoint or a verified append delta, including receipt coordinates, canonical records, new batch membership, projection changes and cumulative capture limits. SQLite-visible changes invalidate that checkpoint; raw file edits bypassing SQLite are outside the warm-read integrity boundary. Unsupported providers, imports, lineage and unproved deltas fall back to complete verification. Scoped facade reads share one operation scope, and multi-subject histories preserve order, duplicates and empty histories for absent subjects. Provisioning, imported anchors, attachment and rebuild are explicit administrative capabilities. A batch of imported boundaries establishes every member in one atomic append group, from one capture and one post-capture, writing byte-identical anchors, blob keys and receipts to the same boundaries imported one at a time; a repeated subject settles as the repeat N singular calls settle, differing anchors for one subject and a record identity two members share are refused from the batch's own capture before any blob is uploaded, the guard takes its keys in key order rather than caller order and is idempotent over a batch-bearing retry of its own commit, a provider that refuses guarded blob-bearing groups gets the same bytes through the per-blob path, a batch that would commit past the capture bounds its own handle reads with refuses before it writes, a blocked batch names the member actually held with the revision it holds, and any refusal leaves none of the batch committed — with no blob of it bound on a provider that binds a group's blobs in the group's transaction. The optional synchronous facade owns its provider on one dedicated runtime thread, bounds admission, distinguishes cancellation before dispatch from write uncertainty after dispatch, contains panics, and reports actual retirement. The adapter/runtime lane requires Rust 1.91 while the pure Entity Runtime workspace remains on Rust 1.85. | design (`eventlog-recorded-adapter-v0.1.md` and its encoding, indexes, errors/import and sync-bridge companions), `literal_reference_vectors_pin_framing_domains_lengths_and_escaping`, `closed_binding_decode_refuses_unknown_fields_and_noncanonical_order`, `a_digest_is_bound_to_its_exact_domain`, `file_provider_provisions_replays_opens_and_rebuilds`, `sqlite_memory_provider_provisions_replays_opens_and_rebuilds`, `sqlite_file_provider_provisions_replays_opens_and_rebuilds`, `postgres_provider_provisions_replays_opens_and_rebuilds_when_assigned`, `synchronous_bridge_owns_reopened_provider_and_reports_retirement`, `unknown_commit_recovery_preserves_a_conclusive_foreign_binding_conflict`, `a_foreign_binding_is_not_a_conflict_until_its_derived_row_is_validated`, `production_worker_startup_owns_and_drops_the_driver_on_its_thread`, `production_worker_calls_are_runtime_independent_and_reentrant_safe`, `production_worker_enforces_capacity_and_discards_cancelled_queue_cells`, `dispatch_deadline_cas_has_one_winner_and_cancelled_work_never_calls_the_driver`, `timed_out_caller_does_not_cancel_commit_and_same_key_retry_recovers_it`, `every_executor_write_keeps_its_original_key_after_dispatch_deadline`, `drain_finishes_in_order_and_escalation_cancels_only_queued_work`, `retirement_timeout_later_joins_once_and_preserves_provider_failure`, `worker_panics_cancel_real_queued_cells_without_stranding_waiters`, `drop_requests_closure_without_waiting_for_an_active_future`, `panicked_or_stopped_worker_keeps_dispatched_write_uncertainty`, `production_worker_preserves_every_driver_error_classification`, `synchronous_bridge_startup_refuses_missing_drifted_and_dirty_projection_admission`, `postgres_bridge_awaits_worker_owned_provider_retirement_when_assigned`, `file_provider_preserves_complete_service_3_composition`, `sqlite_memory_provider_preserves_complete_service_3_composition`, `sqlite_file_provider_preserves_complete_service_3_composition`, `postgres_provider_preserves_complete_service_3_composition_when_assigned`, `sqlite_bridge_crosses_service_3_retry_history_and_reopen`, `a_batch_import_writes_the_same_bytes_as_singular_imports_from_one_capture`, `a_refused_member_leaves_no_part_of_the_batch_committed`, `a_batch_naming_one_subject_twice_with_different_anchors_is_refused_before_any_write`, `an_empty_batch_reaches_no_provider`, `a_guard_refused_batch_binds_no_blob_on_the_sqlite_provider`, `a_batch_refuses_a_record_identity_two_of_its_members_share`, `a_batch_naming_one_subject_twice_settles_as_two_singular_calls_settle`, `the_blob_and_append_mappings_agree_on_the_only_variant_put_blob_documents`, `a_batch_import_commits_through_the_port_default_on_the_sqlite_provider`, `a_batch_bearing_retry_is_admitted_over_the_commit_it_already_made`, `a_partially_overlapping_group_reports_the_member_that_is_actually_occupied`, `a_partially_overlapping_group_commits_no_part_of_itself`, `a_batch_commits_no_more_than_its_own_handle_can_read_back`, `the_port_default_fails_closed_and_binds_no_blob`, `the_sqlite_override_commits_a_group_with_its_blob_and_a_refused_guard_binds_neither`, `the_guard_refuses_a_record_identity_another_writer_committed_after_the_pre_capture`, `the_guard_refuses_a_subject_another_writer_answered_for_under_different_bytes`, `one_anchor_naming_a_record_identity_twice_is_refused`, `a_command_on_one_subject_reads_only_its_own_stream_on_the_tree_provider`, `a_command_on_one_subject_reads_only_its_own_stream_on_the_sqlite_provider`, `a_command_on_one_subject_reads_only_its_own_stream_on_the_file_provider`, `a_command_refuses_a_substituted_stream_identity_on_the_tree_provider`, `a_command_refuses_a_substituted_stream_identity_on_the_sqlite_provider`, `a_command_refuses_a_forged_index_row_of_its_own_subject_on_the_tree_provider`, `a_command_refuses_a_forged_index_row_of_its_own_subject_on_the_sqlite_provider`, `r151_a_command_read_is_per_entity_and_a_handle_read_is_one_complete_capture`, `an_honest_concurrent_writer_is_a_revision_conflict_not_provider_integrity`, `a_handle_does_not_commit_commands_it_can_no_longer_read_back`, `a_read_through_a_forgotten_tenant_does_not_mint_it_a_new_identity`, `tracked_unchanged_reads_and_appends_do_not_recapture_verified_prefixes`, `delta_verification_matches_full_models_and_refuses_missing_or_forged_coordinates`, `held_reader_keeps_its_old_model_while_a_refresh_rebuilds_safely`, `unjournaled_provider_mutation_invalidates_the_model_before_reuse`, `one_scoped_history_read_preserves_order_duplicates_and_absence` | +| R-152 | `service/3` admits a closed, schema-checked operation fulfillment map with Set, Preserve and optional Remove. An opaque continuation requests actions only after exact subject, ordered outcome selection, refusal, state and preconditions pass; one resulting map drives schema, identity, invariants, events and response. Invalid placements and action coordinates are typed refusals. The executor preserves declared input and stored-field refusals when success fulfillment keys are supplied; accepted outcomes still require their exact keys. | design (`service-operation-field-fulfillment-v0.1.md`), `set_preserve_and_remove_apply_once_and_share_the_result_with_event_response_and_replay`, `preserve_keeps_loaded_presence_while_every_wrong_action_coordinate_is_typed`, `selection_refusal_preconditions_and_exact_subject_finish_before_any_fulfillment`, `registration_accumulates_every_fulfillment_placement_and_presence_defect`, `invariants_judge_the_post_action_map_and_event_only_service_3_history_is_refused`, `refusal_ignores_success_fulfillments`, `subject_refusal_ignores_success_fulfillments`, `accepted_outcomes_still_require_exact_fulfillment_keys` | | R-153 | Fulfillment actions and disjoint removal evidence survive complete decision replay, exact retry and restart, including updates and moves with zero or multiple events. Event folding deletes before insertion. `service/3` records and requests use explicit `/4` domains; old readers refuse `/4`, prior kernel/service domains and `er.batch/1` remain unchanged, and event-only service rehydration refuses its unsupported carrier. | design (`service-operation-field-fulfillment-v0.1.md`), `update_and_move_outcomes_record_removal_with_zero_one_and_many_events`, `event_changes_delete_before_they_insert`, `retry_restart_and_verified_history_preserve_set_and_removal_actions`, `service_3_uses_record_4_and_request_4_with_exact_ordered_actions`, `old_readers_refuse_record_and_request_4_at_the_literal_tag`, `kernel_1_and_service_1_keep_their_definitions_decisions_and_refusal_order`, `service_2_uses_literal_record_3_and_request_3_framing`, `mixed_record_domains_remain_members_of_er_batch_1`, `an_older_service_retry_does_not_ignore_new_fulfillment_coordinates`, `a_kernel_event_cannot_invent_service_3_removal_evidence` | | R-154 | A caller may ask Entity Runtime to select a creation outcome before deriving the storage address from that outcome's complete validated logical identity. The selected outcome executes once; pre-address `$id` reads are explicit circular template refusals; and the existing caller-supplied creation API, decision and record bytes, replay and original-request identity retain their contracts. | design (`selected-creation-identity-v0.1.md`), `each_selected_creation_branch_derives_and_publishes_its_own_identity`, `derived_and_supplied_creation_are_one_decision_and_replay_contract`, `runtime_exposes_the_same_derived_capability_without_a_candidate_address`, `pre_address_id_in_a_selector_or_selected_assignment_is_explicitly_circular`, `derivation_requires_a_declared_logical_identity`, `a_derived_identity_keeps_the_existing_record_and_original_request_bytes` | | R-155 | Eventlog-backed File, SQLite and PostgreSQL facades expose complete recorded state, events, observations, history, queries, original receipts, retries and ordered atomic groups without weakening the retained legacy APIs. Open requires an exact immutable authority; provisioning and import are explicit mutations. Read-only legacy File/SQLite/PostgreSQL acquisition preserves every available typed envelope, bare event, identity and only its known order, and import retains limited assurance without inventing a command, receipt or chronology. The File facade's named groups are atomic across competing processes and crash/reopen; PostgreSQL retains caller-owned connection and TLS authority. The `tree` feature opens and provisions the same recorded store on `eventlog-tree`, whose immutable files merge under version control: two branches that recorded different subjects merge into one store that opens and holds both. | design (`eventlog-provider-facades-and-legacy-imports.md`), `tree_provider_provisions_replays_opens_and_rebuilds`, `two_branches_that_recorded_different_subjects_merge_into_one_store`, `file_facade_preserves_atomic_groups_queries_retries_and_restart`, `file_facade_groups_are_process_atomic_and_survive_a_publication_crash`, `sqlite_eventlog_facade_entrypoints_preserve_receipts_queries_and_exact_reopen`, `sqlite_file_provision_returns_the_exact_reopen_authority`, `legacy_file_import_is_exact_resumable_and_provider_verified`, `legacy_import_retains_settled_progress_and_refuses_a_changed_source_boundary`, `legacy_import_revalidates_the_complete_source_before_any_destination_write`, `file_acquisition_retains_complete_envelopes_and_only_known_order`, `sqlite_acquisition_retains_mixed_history_without_inventing_event_order`, `legacy_acquisition_retains_exact_available_postgres_evidence`, `postgres_eventlog_facade_preserves_caller_owned_transport_and_complete_receipts_when_assigned`, `explicit_eventlog_file_selection_provisions_creates_executes_retries_and_lists` | diff --git a/ess/coverage.json b/ess/coverage.json index 4a673643..a8766c39 100644 --- a/ess/coverage.json +++ b/ess/coverage.json @@ -1,6 +1,6 @@ { "format": "er-ess-coverage/2", - "review": "Reviewed requirement clauses, real continuation and replay success cases, exact runtime values, and complete per-scenario assertion fingerprints; retain all five production mutations and independent correction reviews.", + "review": "Add explicit-version executor refusal ordering, atomic rollback, malformed-version and merge-proof scenarios for issue 50", "scenarios": [ "entity.core.Address/outcome/returned", "entity.core.CompareNumbers/outcome/returned", @@ -245,10 +245,15 @@ "entity.executor/authored/duplicate-identities-refuse-before-io", "entity.executor/authored/empty-batches-perform-no-io", "entity.executor/authored/empty-history-is-not-a-merge", + "entity.executor/authored/executor-input-refusal-existing-create", + "entity.executor/authored/executor-input-refusal-missing-execute", "entity.executor/authored/global-record-identities-span-subjects-and-kinds", "entity.executor/authored/historical-observation-has-no-committed-receipt", "entity.executor/authored/imported-exact-retry-is-historical", "entity.executor/authored/imported-missing-definition-cannot-prove-retry", + "entity.executor/authored/issue-50-batch-refusal-is-atomic", + "entity.executor/authored/issue-50-malformed-version", + "entity.executor/authored/issue-50-versioned-merge-preserves-proof", "entity.executor/authored/kernel-refusal-is-recorded", "entity.executor/authored/malformed-create-is-rejected-before-io", "entity.executor/authored/merge-appends-over-observation-tips", @@ -258,12 +263,14 @@ "entity.executor/authored/named-and-single-retries-keep-original-receipts", "entity.executor/authored/optional-refusal-recorder-sees-conflicts-not-malformed-input", "entity.executor/authored/ordinary-forked-write-records-refusal", + "entity.executor/authored/refusal-ignores-success-fulfillments", "entity.executor/authored/saved-definition-retry-precedes-current-authority", "entity.executor/authored/service-1-saved-definition-retry", "entity.executor/authored/service-2-conditional-presence-retry", "entity.executor/authored/service-3-fulfillment-retry", "entity.executor/authored/single-key-must-match-member", "entity.executor/authored/stale-observation-rolls-back-whole-batch", + "entity.executor/authored/subject-refusal-ignores-success-fulfillments", "entity.executor/authored/uncertain-response-recovers-one-original-receipt", "entity.executor/authored/unreachable-is-never-absent", "entity.query.Continue/outcome/returned", @@ -661,10 +668,15 @@ "entity.executor/authored/duplicate-identities-refuse-before-io": "sha256:229554d895311748a9befa52306dd53d792bad0ab9ab095c7cc2bb9bd5195dd2", "entity.executor/authored/empty-batches-perform-no-io": "sha256:3e23338389bcbb62bb260577277fa7459457aa7545430eeedb682155594b5c29", "entity.executor/authored/empty-history-is-not-a-merge": "sha256:aa9f08368b975d3f45ef094255f524cc9fb86718f95ad5a643b084866eb5f474", + "entity.executor/authored/executor-input-refusal-existing-create": "sha256:896f501ed47ede3ffaf4bb365716dc06ffb78cb0de2960cf208757c9c907eda3", + "entity.executor/authored/executor-input-refusal-missing-execute": "sha256:8b4377ecf445b49eebc7a62ace6ff960f5cd8cee370d64d22f16bc674d29c962", "entity.executor/authored/global-record-identities-span-subjects-and-kinds": "sha256:2e59ea3ab767fec98e4a418824c129faa32ad39fdbda3417f07a4808b157d65d", "entity.executor/authored/historical-observation-has-no-committed-receipt": "sha256:007752a027c66f2da537aa48f9f3a6032a4f67fb4cdd9bfe764a449d3a8772ec", "entity.executor/authored/imported-exact-retry-is-historical": "sha256:656b5f90b90393b9526000f834de3245e977ef0e31c51771a91a7f7ef0a5fd96", "entity.executor/authored/imported-missing-definition-cannot-prove-retry": "sha256:9d35b58a4f652f468e099c6efabd7b94032e0c00657fe9c43305770123945914", + "entity.executor/authored/issue-50-batch-refusal-is-atomic": "sha256:86e4c503d08738d7086097227ed22eccecf3f2abddd9dd64d86f3f3515b0db18", + "entity.executor/authored/issue-50-malformed-version": "sha256:6484429e5c9fe498713fc21249539f96d97dc7bca8835552d9521bf040f1a7a0", + "entity.executor/authored/issue-50-versioned-merge-preserves-proof": "sha256:3e8e5314a0129f35bcd1d3b8b0a51934a685ad34207b68c2a7adbd4410c70f46", "entity.executor/authored/kernel-refusal-is-recorded": "sha256:af9752e41b3af5a2636aad98a6892264e67bd3443507c7a45a800df472b614e9", "entity.executor/authored/malformed-create-is-rejected-before-io": "sha256:6913f1740573578138d28aecfb9a7bc0270e0f3eb6a242c85755b5857a130bb0", "entity.executor/authored/merge-appends-over-observation-tips": "sha256:dc686c1ea626742f2ff366000ebd15d89f9ee695494f94b2ea4c8519fe3d33f3", @@ -674,12 +686,14 @@ "entity.executor/authored/named-and-single-retries-keep-original-receipts": "sha256:a8bd81e6755300748e853d363e281aa444e59e6330cc6fc0fde36361e6299c44", "entity.executor/authored/optional-refusal-recorder-sees-conflicts-not-malformed-input": "sha256:9553f6926978d24fc151daaaecf818de7ad34b0df9fe29022680c5f36d7f0def", "entity.executor/authored/ordinary-forked-write-records-refusal": "sha256:8976730108ae74e3d9bd66cceadf1045c1b54d0c6b48655ea23e33ac551ac79f", + "entity.executor/authored/refusal-ignores-success-fulfillments": "sha256:45be06d80dc1317e570103a35a37f91867e2ca53d26fddd09e6a17e856f66503", "entity.executor/authored/saved-definition-retry-precedes-current-authority": "sha256:96118ee0814f16e96bc78b7ed1ec8d60caff6e6080131367d1e56727f0794d85", "entity.executor/authored/service-1-saved-definition-retry": "sha256:09b52a2fa9e0d3345ad8700b6bf51a415c9b1959fe0959708661df9809ede677", "entity.executor/authored/service-2-conditional-presence-retry": "sha256:bdae1378ff221b29f1c9bc1a9a8533f9eef41719230ed24514289737379d4928", "entity.executor/authored/service-3-fulfillment-retry": "sha256:fa167832c8bf9f5594a1a7c9dd25d8fd1cd0ef1f3e120f0a11617ec76f8471da", "entity.executor/authored/single-key-must-match-member": "sha256:f540f04e79289f5b3b3de19a2f2f3746a7f9d144371e705dee187919f6257f04", "entity.executor/authored/stale-observation-rolls-back-whole-batch": "sha256:b7523d624543b56f05622f1e88defb5bf92b5caecd933a35b803c793ac6f2bf3", + "entity.executor/authored/subject-refusal-ignores-success-fulfillments": "sha256:0f23918f476e0426c38bf2ecb8a328f47178d2474528ac3870db5619da167afc", "entity.executor/authored/uncertain-response-recovers-one-original-receipt": "sha256:d197823c9c09e0c9fb99f262107dbd765b7ef203f4f8e699936f503410f66d94", "entity.executor/authored/unreachable-is-never-absent": "sha256:838222b3b6987d0c30b473af9561317aa6cc272413e3c84d037292f99d3f51af", "entity.query.Continue/outcome/returned": "sha256:7deee50ce9b845428b49d2aeb1f89ff9f2e5b9d07824e005a8863746945cb130", diff --git a/ess/ess-inputs.yaml b/ess/ess-inputs.yaml index 1a57b1ea..436c60fb 100644 --- a/ess/ess-inputs.yaml +++ b/ess/ess-inputs.yaml @@ -229,6 +229,8 @@ scenarios: - scenarios/executor/service-1-saved-definition-retry.yaml - scenarios/executor/service-2-conditional-presence-retry.yaml - scenarios/executor/service-3-fulfillment-retry.yaml + - scenarios/executor/refusal-ignores-success-fulfillments.yaml + - scenarios/executor/subject-refusal-ignores-success-fulfillments.yaml - scenarios/executor/single-key-must-match-member.yaml - scenarios/executor/stale-observation-rolls-back-whole-batch.yaml - scenarios/executor/uncertain-response-recovers-one-original-receipt.yaml @@ -269,6 +271,11 @@ scenarios: - scenarios/shell/memory-stale-intent-precedes-kernel.yaml - scenarios/shell/provider-failures-preserve-classification.yaml - scenarios/shell/retry-does-not-hide-read-failure.yaml + - scenarios/executor/issue-50-input-refusal-existing-create.yaml + - scenarios/executor/issue-50-input-refusal-missing-execute.yaml + - scenarios/executor/issue-50-batch-refusal-is-atomic.yaml + - scenarios/executor/issue-50-malformed-version.yaml + - scenarios/executor/issue-50-versioned-merge-preserves-proof.yaml - scenarios/store/absence.yaml - scenarios/store/canonical-byte-encoding-preserves-exact-number-tokens.yaml - scenarios/store/complete-assurance-requires-provider-capture.yaml diff --git a/ess/generated/suite.json b/ess/generated/suite.json index 7c0a226d..d64f3877 100644 --- a/ess/generated/suite.json +++ b/ess/generated/suite.json @@ -198,10 +198,15 @@ "entity.executor/authored/duplicate-identities-refuse-before-io", "entity.executor/authored/empty-batches-perform-no-io", "entity.executor/authored/empty-history-is-not-a-merge", + "entity.executor/authored/executor-input-refusal-existing-create", + "entity.executor/authored/executor-input-refusal-missing-execute", "entity.executor/authored/global-record-identities-span-subjects-and-kinds", "entity.executor/authored/historical-observation-has-no-committed-receipt", "entity.executor/authored/imported-exact-retry-is-historical", "entity.executor/authored/imported-missing-definition-cannot-prove-retry", + "entity.executor/authored/issue-50-batch-refusal-is-atomic", + "entity.executor/authored/issue-50-malformed-version", + "entity.executor/authored/issue-50-versioned-merge-preserves-proof", "entity.executor/authored/kernel-refusal-is-recorded", "entity.executor/authored/malformed-create-is-rejected-before-io", "entity.executor/authored/merge-appends-over-observation-tips", @@ -211,12 +216,14 @@ "entity.executor/authored/named-and-single-retries-keep-original-receipts", "entity.executor/authored/optional-refusal-recorder-sees-conflicts-not-malformed-input", "entity.executor/authored/ordinary-forked-write-records-refusal", + "entity.executor/authored/refusal-ignores-success-fulfillments", "entity.executor/authored/saved-definition-retry-precedes-current-authority", "entity.executor/authored/service-1-saved-definition-retry", "entity.executor/authored/service-2-conditional-presence-retry", "entity.executor/authored/service-3-fulfillment-retry", "entity.executor/authored/single-key-must-match-member", "entity.executor/authored/stale-observation-rolls-back-whole-batch", + "entity.executor/authored/subject-refusal-ignores-success-fulfillments", "entity.executor/authored/uncertain-response-recovers-one-original-receipt", "entity.executor/authored/unreachable-is-never-absent", "entity.query/authored/consumed-descending-identity-refuses", @@ -1311,6 +1318,31 @@ "disposition": "accepted", "scenario": "entity.executor/authored/imported-missing-definition-cannot-prove-retry" }, + "scenarios/executor/issue-50-batch-refusal-is-atomic.yaml": { + "digest": "sha256:75f86034da77fe4e10e4ba4edbf514f709775a0bb4454bd93829fff3ae8bcb76", + "disposition": "accepted", + "scenario": "entity.executor/authored/issue-50-batch-refusal-is-atomic" + }, + "scenarios/executor/issue-50-input-refusal-existing-create.yaml": { + "digest": "sha256:2e72561952be0cf26339e153df9da74796c9a5286665b0c056afdf0acf2f7e45", + "disposition": "accepted", + "scenario": "entity.executor/authored/executor-input-refusal-existing-create" + }, + "scenarios/executor/issue-50-input-refusal-missing-execute.yaml": { + "digest": "sha256:b3ef101ac62fed8dd48d5db9c2603ac761a271cb63ed1f48050ce0e2abb239a5", + "disposition": "accepted", + "scenario": "entity.executor/authored/executor-input-refusal-missing-execute" + }, + "scenarios/executor/issue-50-malformed-version.yaml": { + "digest": "sha256:bfd9679421eea245d4dda01c485c9b2ae81e64ef80cac21e2d8f69d3f8406779", + "disposition": "accepted", + "scenario": "entity.executor/authored/issue-50-malformed-version" + }, + "scenarios/executor/issue-50-versioned-merge-preserves-proof.yaml": { + "digest": "sha256:a3aa6da51558cd2dfb1e48a27cb41ca6a8b344ef7ef179aa4c0f17081114a039", + "disposition": "accepted", + "scenario": "entity.executor/authored/issue-50-versioned-merge-preserves-proof" + }, "scenarios/executor/kernel-refusal-is-recorded.yaml": { "digest": "sha256:5d8b3a3c32bf930b020b49d9d27cf023436cbce28fdbbe03fbd49a059c507790", "disposition": "accepted", @@ -1356,6 +1388,11 @@ "disposition": "accepted", "scenario": "entity.executor/authored/ordinary-forked-write-records-refusal" }, + "scenarios/executor/refusal-ignores-success-fulfillments.yaml": { + "digest": "sha256:6247ca7b983dac8637cc6e313a24a6bdbb02ea40f46f49b80132fb76238f6086", + "disposition": "accepted", + "scenario": "entity.executor/authored/refusal-ignores-success-fulfillments" + }, "scenarios/executor/saved-definition-retry-precedes-current-authority.yaml": { "digest": "sha256:5cbaa4ae899865dc4b19997666390b75e73dd85ac3c7335b76c96e98885b9447", "disposition": "accepted", @@ -1386,6 +1423,11 @@ "disposition": "accepted", "scenario": "entity.executor/authored/stale-observation-rolls-back-whole-batch" }, + "scenarios/executor/subject-refusal-ignores-success-fulfillments.yaml": { + "digest": "sha256:932c6d40f8b4ce74475f40dd88053f86a4e6654b1d5f14866c03ba5718a8a430", + "disposition": "accepted", + "scenario": "entity.executor/authored/subject-refusal-ignores-success-fulfillments" + }, "scenarios/executor/uncertain-response-recovers-one-original-receipt.yaml": { "digest": "sha256:ce651ad505fa0a5fc020d0a607c9e9907f4cfd3988a25956b2dadb7c93c7e464", "disposition": "accepted", @@ -1823,7 +1865,7 @@ } }, "counts": { - "authored": 303, + "authored": 310, "generated": 111, "outside": 0, "refused": 0 @@ -54048,8 +54090,8 @@ } ] }, - "entity.executor/authored/global-record-identities-span-subjects-and-kinds": { - "purpose": "One global record ID cannot be reused as another subject's creation or an observation.", + "entity.executor/authored/executor-input-refusal-existing-create": { + "purpose": "A declared creation refusal precedes existence and leaves the original row and history intact.", "source": [ { "kind": "domain", @@ -54063,6 +54105,10 @@ "kind": "type", "name": "entity.executor.RequestDocument" }, + { + "kind": "command", + "name": "entity.executor.ClearTrace" + }, { "kind": "command", "name": "entity.executor.Create" @@ -54073,12 +54119,23 @@ }, { "kind": "command", - "name": "entity.executor.Observe" + "name": "entity.executor.LookupRecord" }, { "kind": "command", "name": "entity.executor.Register" }, + { + "kind": "command", + "name": "entity.executor.Trace" + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.ClearTrace", + "outcome": "returned" + } + }, { "kind": "outcome", "name": { @@ -54096,7 +54153,7 @@ { "kind": "outcome", "name": { - "command": "entity.executor.Observe", + "command": "entity.executor.LookupRecord", "outcome": "returned" } }, @@ -54106,6 +54163,13 @@ "command": "entity.executor.Register", "outcome": "returned" } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Trace", + "outcome": "returned" + } } ], "steps": [ @@ -54114,7 +54178,7 @@ "input": { "request": { "kind": "literal", - "value": "{\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1}}" + "value": "{\"definition\":{\"entity\":\"invoice\",\"version\":1,\"semantics\":\"service/3\",\"schema\":{\"fields\":{}},\"lifecycle\":{\"initial\":\"Open\",\"states\":[\"Open\"]},\"create\":{\"arguments\":{\"fields\":{\"accept\":{\"type\":\"boolean\",\"required\":true}}},\"outcomes\":[{\"name\":\"rejected\",\"when\":{\"eq\":[\"$args.accept\",false]},\"refuses\":{\"error\":\"InputRejected\",\"message\":\"Input was refused\"}},{\"name\":\"created\",\"effect\":\"creates\"}]}}}" } }, "step": "execute_command" @@ -54136,7 +54200,6 @@ } }, "expected": { - "error": "", "status": "ok" }, "fields": [ @@ -54165,7 +54228,7 @@ "input": { "request": { "kind": "literal", - "value": "{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"one\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"version\":1}" + "value": "{\"entity\":\"invoice\",\"id\":\"a\",\"version\":1,\"fields\":{\"accept\":true},\"recording\":{\"record_id\":\"created\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}" } }, "step": "execute_command" @@ -54187,7 +54250,6 @@ } }, "expected": { - "error": "", "status": "ok" }, "fields": [ @@ -54212,25 +54274,25 @@ "step": "expect_direct_response" }, { - "command": "entity.executor.Create", + "command": "entity.executor.ClearTrace", "input": { "request": { "kind": "literal", - "value": "{\"entity\":\"ticket\",\"fields\":{\"title\":\"b\"},\"id\":\"b\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"one\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"version\":1}" + "value": "{}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.Create", + "command": "entity.executor.ClearTrace", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.Create", + "command": "entity.executor.ClearTrace", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -54238,8 +54300,7 @@ } }, "expected": { - "error": "RecordConflict", - "status": "error" + "status": "ok" }, "fields": [ { @@ -54256,32 +54317,32 @@ } ], "outcome": { - "command": "entity.executor.Create", + "command": "entity.executor.ClearTrace", "outcome": "returned" } }, "step": "expect_direct_response" }, { - "command": "entity.executor.Observe", + "command": "entity.executor.Create", "input": { "request": { "kind": "literal", - "value": "{\"observation\":{\"entity\":\"ticket\",\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"evidence\":null},\"record_id\":\"one\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"id\":\"a\",\"revision\":1}}" + "value": "{\"entity\":\"invoice\",\"id\":\"a\",\"version\":1,\"fields\":{\"accept\":false},\"recording\":{\"record_id\":\"refused\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.Observe", + "command": "entity.executor.Create", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.Observe", + "command": "entity.executor.Create", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -54289,8 +54350,9 @@ } }, "expected": { - "error": "RecordConflict", - "status": "error" + "error": "Refused", + "status": "error", + "value": "{\"detail\":\"outcome 'rejected' refuses with 'InputRejected': Input was refused\"}" }, "fields": [ { @@ -54307,32 +54369,32 @@ } ], "outcome": { - "command": "entity.executor.Observe", + "command": "entity.executor.Create", "outcome": "returned" } }, "step": "expect_direct_response" }, { - "command": "entity.executor.Load", + "command": "entity.executor.Trace", "input": { "request": { "kind": "literal", - "value": "{\"entity\":\"ticket\",\"id\":\"b\"}" + "value": "{}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.Load", + "command": "entity.executor.Trace", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.Load", + "command": "entity.executor.Trace", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -54340,9 +54402,8 @@ } }, "expected": { - "error": "", "status": "ok", - "value": "null" + "value": "[\"lookup_batch\",\"lookup_record:refused\"]" }, "fields": [ { @@ -54359,73 +54420,32 @@ } ], "outcome": { - "command": "entity.executor.Load", + "command": "entity.executor.Trace", "outcome": "returned" } }, "step": "expect_direct_response" - } - ] - }, - "entity.executor/authored/historical-observation-has-no-committed-receipt": { - "purpose": "An exact imported observation returns Historical with explicit boundary assurance and preserved evidence.", - "source": [ - { - "kind": "domain", - "name": "entity.executor" }, { - "kind": "type", - "name": "entity.core.JsonDocument" - }, - { - "kind": "type", - "name": "entity.executor.RequestDocument" - }, - { - "kind": "command", - "name": "entity.executor.Observe" - }, - { - "kind": "command", - "name": "entity.executor.SeedImported" - }, - { - "kind": "outcome", - "name": { - "command": "entity.executor.Observe", - "outcome": "returned" - } - }, - { - "kind": "outcome", - "name": { - "command": "entity.executor.SeedImported", - "outcome": "returned" - } - } - ], - "steps": [ - { - "command": "entity.executor.SeedImported", + "command": "entity.executor.Load", "input": { "request": { "kind": "literal", - "value": "{\"history\":{\"origin\":{\"imported\":{\"completeness\":\"available_evidence_only\",\"evidence\":[{\"envelope\":{\"entry\":{\"observation\":{\"entity\":\"ticket\",\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"evidence\":null},\"record_id\":\"historical-observation\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"id\":\"a\",\"revision\":8}},\"known_order\":{\"per_kind\":0},\"source_id\":\"old\",\"source_locator\":\"observation/0\"}}],\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":8,\"version\":1},\"order\":\"per_kind_only\"}},\"records\":[],\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}}" + "value": "{\"entity\":\"invoice\",\"id\":\"a\"}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.SeedImported", + "command": "entity.executor.Load", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.SeedImported", + "command": "entity.executor.Load", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -54433,8 +54453,8 @@ } }, "expected": { - "error": "", - "status": "ok" + "status": "ok", + "value": "{\"entity\":\"invoice\",\"fields\":{},\"id\":\"a\",\"lifecycle_state\":\"Open\",\"revision\":1,\"version\":1}" }, "fields": [ { @@ -54451,32 +54471,32 @@ } ], "outcome": { - "command": "entity.executor.SeedImported", + "command": "entity.executor.Load", "outcome": "returned" } }, "step": "expect_direct_response" }, { - "command": "entity.executor.Observe", + "command": "entity.executor.LookupRecord", "input": { "request": { "kind": "literal", - "value": "{\"observation\":{\"entity\":\"ticket\",\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"evidence\":null},\"record_id\":\"historical-observation\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"id\":\"a\",\"revision\":8}}" + "value": "{\"record_id\":\"refused\"}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.Observe", + "command": "entity.executor.LookupRecord", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.Observe", + "command": "entity.executor.LookupRecord", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -54484,9 +54504,8 @@ } }, "expected": { - "error": "", "status": "ok", - "value": "{\"assurance\":{\"anchor_revision\":8,\"kind\":\"VerifiedAfterBoundary\",\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}},\"evidence\":{\"entry\":{\"observation\":{\"entity\":\"ticket\",\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"evidence\":null},\"record_id\":\"historical-observation\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"id\":\"a\",\"revision\":8}},\"known_order\":{\"per_kind\":0},\"source_id\":\"old\",\"source_locator\":\"observation/0\"},\"kind\":\"Historical\"}" + "value": "null" }, "fields": [ { @@ -54503,7 +54522,7 @@ } ], "outcome": { - "command": "entity.executor.Observe", + "command": "entity.executor.LookupRecord", "outcome": "returned" } }, @@ -54511,8 +54530,8 @@ } ] }, - "entity.executor/authored/imported-exact-retry-is-historical": { - "purpose": "Available imported envelopes can prove historical retry without fabricated committed receipt coordinates.", + "entity.executor/authored/executor-input-refusal-missing-execute": { + "purpose": "An explicit definition selects the declared input refusal before a missing subject is loaded.", "source": [ { "kind": "domain", @@ -54528,11 +54547,15 @@ }, { "kind": "command", - "name": "entity.executor.Create" + "name": "entity.executor.Execute" }, { "kind": "command", - "name": "entity.executor.LookupReceipt" + "name": "entity.executor.Load" + }, + { + "kind": "command", + "name": "entity.executor.LookupRecord" }, { "kind": "command", @@ -54540,19 +54563,26 @@ }, { "kind": "command", - "name": "entity.executor.SeedImported" + "name": "entity.executor.Trace" }, { "kind": "outcome", "name": { - "command": "entity.executor.Create", + "command": "entity.executor.Execute", "outcome": "returned" } }, { "kind": "outcome", "name": { - "command": "entity.executor.LookupReceipt", + "command": "entity.executor.Load", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.LookupRecord", "outcome": "returned" } }, @@ -54566,7 +54596,7 @@ { "kind": "outcome", "name": { - "command": "entity.executor.SeedImported", + "command": "entity.executor.Trace", "outcome": "returned" } } @@ -54577,7 +54607,7 @@ "input": { "request": { "kind": "literal", - "value": "{\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1}}" + "value": "{\"definition\":{\"entity\":\"invoice\",\"version\":1,\"semantics\":\"service/3\",\"schema\":{\"fields\":{}},\"lifecycle\":{\"initial\":\"Open\",\"states\":[\"Open\"]},\"operations\":{\"Pay\":{\"arguments\":{\"fields\":{\"accept\":{\"type\":\"boolean\",\"required\":true}}},\"outcomes\":[{\"name\":\"rejected\",\"when\":{\"eq\":[\"$args.accept\",false]},\"refuses\":{\"error\":\"InputRejected\",\"message\":\"Input was refused\"}},{\"name\":\"paid\",\"effect\":\"updates\"}]}}}}" } }, "step": "execute_command" @@ -54599,7 +54629,6 @@ } }, "expected": { - "error": "", "status": "ok" }, "fields": [ @@ -54624,25 +54653,25 @@ "step": "expect_direct_response" }, { - "command": "entity.executor.SeedImported", + "command": "entity.executor.Execute", "input": { "request": { "kind": "literal", - "value": "{\"history\":{\"origin\":{\"imported\":{\"completeness\":\"available_evidence_only\",\"evidence\":[{\"envelope\":{\"entry\":{\"decision\":{\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"changed\":{\"title\":\"a\"},\"command\":{\"command\":\"create\",\"fields\":{\"title\":\"a\"}},\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1},\"entity\":\"ticket\",\"events\":[],\"from_state\":null,\"id\":\"a\",\"result\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1},\"revision\":1,\"to_state\":\"open\"},\"record_id\":\"historical\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1}}},\"known_order\":{\"per_kind\":0},\"source_id\":\"old\",\"source_locator\":\"ticket/a\"}}],\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1},\"order\":\"per_kind_only\"}},\"records\":[],\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}}" + "value": "{\"entity\":\"invoice\",\"id\":\"missing\",\"definition_version\":1,\"expected_revision\":1,\"operation\":\"Pay\",\"arguments\":{\"accept\":false},\"recording\":{\"record_id\":\"refused\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.SeedImported", + "command": "entity.executor.Execute", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.SeedImported", + "command": "entity.executor.Execute", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -54650,8 +54679,9 @@ } }, "expected": { - "error": "", - "status": "ok" + "error": "Refused", + "status": "error", + "value": "{\"detail\":\"outcome 'rejected' refuses with 'InputRejected': Input was refused\"}" }, "fields": [ { @@ -54668,32 +54698,32 @@ } ], "outcome": { - "command": "entity.executor.SeedImported", + "command": "entity.executor.Execute", "outcome": "returned" } }, "step": "expect_direct_response" }, { - "command": "entity.executor.Create", + "command": "entity.executor.Trace", "input": { "request": { "kind": "literal", - "value": "{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"historical\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"version\":1}" + "value": "{}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.Create", + "command": "entity.executor.Trace", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.Create", + "command": "entity.executor.Trace", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -54701,8 +54731,8 @@ } }, "expected": { - "error": "", - "status": "ok" + "status": "ok", + "value": "[\"lookup_batch\",\"lookup_record:refused\"]" }, "fields": [ { @@ -54719,32 +54749,32 @@ } ], "outcome": { - "command": "entity.executor.Create", + "command": "entity.executor.Trace", "outcome": "returned" } }, "step": "expect_direct_response" }, { - "command": "entity.executor.LookupReceipt", + "command": "entity.executor.Load", "input": { "request": { "kind": "literal", - "value": "{\"record_id\":\"historical\"}" + "value": "{\"entity\":\"invoice\",\"id\":\"missing\"}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.LookupReceipt", + "command": "entity.executor.Load", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.LookupReceipt", + "command": "entity.executor.Load", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -54752,9 +54782,8 @@ } }, "expected": { - "error": "", "status": "ok", - "value": "{\"evidence\":{\"entry\":{\"decision\":{\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"changed\":{\"title\":\"a\"},\"command\":{\"command\":\"create\",\"fields\":{\"title\":\"a\"}},\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1},\"entity\":\"ticket\",\"events\":[],\"from_state\":null,\"id\":\"a\",\"result\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1},\"revision\":1,\"to_state\":\"open\"},\"record_id\":\"historical\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1}}},\"known_order\":{\"per_kind\":0},\"source_id\":\"old\",\"source_locator\":\"ticket/a\"},\"kind\":\"Imported\"}" + "value": "null" }, "fields": [ { @@ -54771,7 +54800,58 @@ } ], "outcome": { - "command": "entity.executor.LookupReceipt", + "command": "entity.executor.Load", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.LookupRecord", + "input": { + "request": { + "kind": "literal", + "value": "{\"record_id\":\"refused\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.LookupRecord", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "null" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.LookupRecord", "outcome": "returned" } }, @@ -54779,8 +54859,8 @@ } ] }, - "entity.executor/authored/imported-missing-definition-cannot-prove-retry": { - "purpose": "An imported envelope lacking its saved definition cannot report historical retry success.", + "entity.executor/authored/global-record-identities-span-subjects-and-kinds": { + "purpose": "One global record ID cannot be reused as another subject's creation or an observation.", "source": [ { "kind": "domain", @@ -54800,15 +54880,15 @@ }, { "kind": "command", - "name": "entity.executor.Register" + "name": "entity.executor.Load" }, { "kind": "command", - "name": "entity.executor.SeedImported" + "name": "entity.executor.Observe" }, { "kind": "command", - "name": "entity.executor.Tamper" + "name": "entity.executor.Register" }, { "kind": "outcome", @@ -54820,21 +54900,21 @@ { "kind": "outcome", "name": { - "command": "entity.executor.Register", + "command": "entity.executor.Load", "outcome": "returned" } }, { "kind": "outcome", "name": { - "command": "entity.executor.SeedImported", + "command": "entity.executor.Observe", "outcome": "returned" } }, { "kind": "outcome", "name": { - "command": "entity.executor.Tamper", + "command": "entity.executor.Register", "outcome": "returned" } } @@ -54891,12 +54971,258 @@ }, "step": "expect_direct_response" }, + { + "command": "entity.executor.Create", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"one\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"version\":1}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Create", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Create", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"fields\":{\"title\":\"b\"},\"id\":\"b\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"one\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"version\":1}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Create", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "RecordConflict", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Observe", + "input": { + "request": { + "kind": "literal", + "value": "{\"observation\":{\"entity\":\"ticket\",\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"evidence\":null},\"record_id\":\"one\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"id\":\"a\",\"revision\":1}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Observe", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Observe", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "RecordConflict", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Observe", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Load", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"b\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Load", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Load", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok", + "value": "null" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Load", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity.executor/authored/historical-observation-has-no-committed-receipt": { + "purpose": "An exact imported observation returns Historical with explicit boundary assurance and preserved evidence.", + "source": [ + { + "kind": "domain", + "name": "entity.executor" + }, + { + "kind": "type", + "name": "entity.core.JsonDocument" + }, + { + "kind": "type", + "name": "entity.executor.RequestDocument" + }, + { + "kind": "command", + "name": "entity.executor.Observe" + }, + { + "kind": "command", + "name": "entity.executor.SeedImported" + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Observe", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.SeedImported", + "outcome": "returned" + } + } + ], + "steps": [ { "command": "entity.executor.SeedImported", "input": { "request": { "kind": "literal", - "value": "{\"history\":{\"origin\":{\"imported\":{\"completeness\":\"available_evidence_only\",\"evidence\":[{\"envelope\":{\"entry\":{\"decision\":{\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"changed\":{\"title\":\"a\"},\"command\":{\"command\":\"create\",\"fields\":{\"title\":\"a\"}},\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1},\"entity\":\"ticket\",\"events\":[],\"from_state\":null,\"id\":\"a\",\"result\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1},\"revision\":1,\"to_state\":\"open\"},\"record_id\":\"historical\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1}}},\"known_order\":{\"per_kind\":0},\"source_id\":\"old\",\"source_locator\":\"ticket/a\"}}],\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1},\"order\":\"per_kind_only\"}},\"records\":[],\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}}" + "value": "{\"history\":{\"origin\":{\"imported\":{\"completeness\":\"available_evidence_only\",\"evidence\":[{\"envelope\":{\"entry\":{\"observation\":{\"entity\":\"ticket\",\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"evidence\":null},\"record_id\":\"historical-observation\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"id\":\"a\",\"revision\":8}},\"known_order\":{\"per_kind\":0},\"source_id\":\"old\",\"source_locator\":\"observation/0\"}}],\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":8,\"version\":1},\"order\":\"per_kind_only\"}},\"records\":[],\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}}" } }, "step": "execute_command" @@ -54943,25 +55269,140 @@ "step": "expect_direct_response" }, { - "command": "entity.executor.Tamper", + "command": "entity.executor.Observe", "input": { "request": { "kind": "literal", - "value": "{\"field\":\"definition\",\"record_id\":\"historical\"}" + "value": "{\"observation\":{\"entity\":\"ticket\",\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"evidence\":null},\"record_id\":\"historical-observation\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"id\":\"a\",\"revision\":8}}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.Tamper", + "command": "entity.executor.Observe", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.Tamper", + "command": "entity.executor.Observe", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok", + "value": "{\"assurance\":{\"anchor_revision\":8,\"kind\":\"VerifiedAfterBoundary\",\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}},\"evidence\":{\"entry\":{\"observation\":{\"entity\":\"ticket\",\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"evidence\":null},\"record_id\":\"historical-observation\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"id\":\"a\",\"revision\":8}},\"known_order\":{\"per_kind\":0},\"source_id\":\"old\",\"source_locator\":\"observation/0\"},\"kind\":\"Historical\"}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Observe", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity.executor/authored/imported-exact-retry-is-historical": { + "purpose": "Available imported envelopes can prove historical retry without fabricated committed receipt coordinates.", + "source": [ + { + "kind": "domain", + "name": "entity.executor" + }, + { + "kind": "type", + "name": "entity.core.JsonDocument" + }, + { + "kind": "type", + "name": "entity.executor.RequestDocument" + }, + { + "kind": "command", + "name": "entity.executor.Create" + }, + { + "kind": "command", + "name": "entity.executor.LookupReceipt" + }, + { + "kind": "command", + "name": "entity.executor.Register" + }, + { + "kind": "command", + "name": "entity.executor.SeedImported" + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Create", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.LookupReceipt", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Register", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.SeedImported", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity.executor.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Register", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -54987,7 +55428,58 @@ } ], "outcome": { - "command": "entity.executor.Tamper", + "command": "entity.executor.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.SeedImported", + "input": { + "request": { + "kind": "literal", + "value": "{\"history\":{\"origin\":{\"imported\":{\"completeness\":\"available_evidence_only\",\"evidence\":[{\"envelope\":{\"entry\":{\"decision\":{\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"changed\":{\"title\":\"a\"},\"command\":{\"command\":\"create\",\"fields\":{\"title\":\"a\"}},\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1},\"entity\":\"ticket\",\"events\":[],\"from_state\":null,\"id\":\"a\",\"result\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1},\"revision\":1,\"to_state\":\"open\"},\"record_id\":\"historical\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1}}},\"known_order\":{\"per_kind\":0},\"source_id\":\"old\",\"source_locator\":\"ticket/a\"}}],\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1},\"order\":\"per_kind_only\"}},\"records\":[],\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.SeedImported", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.SeedImported", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.SeedImported", "outcome": "returned" } }, @@ -55020,8 +55512,8 @@ } }, "expected": { - "error": "HistoricalRetryUnverifiable", - "status": "error" + "error": "", + "status": "ok" }, "fields": [ { @@ -55043,6 +55535,1179 @@ } }, "step": "expect_direct_response" + }, + { + "command": "entity.executor.LookupReceipt", + "input": { + "request": { + "kind": "literal", + "value": "{\"record_id\":\"historical\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.LookupReceipt", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.LookupReceipt", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok", + "value": "{\"evidence\":{\"entry\":{\"decision\":{\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"changed\":{\"title\":\"a\"},\"command\":{\"command\":\"create\",\"fields\":{\"title\":\"a\"}},\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1},\"entity\":\"ticket\",\"events\":[],\"from_state\":null,\"id\":\"a\",\"result\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1},\"revision\":1,\"to_state\":\"open\"},\"record_id\":\"historical\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1}}},\"known_order\":{\"per_kind\":0},\"source_id\":\"old\",\"source_locator\":\"ticket/a\"},\"kind\":\"Imported\"}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.LookupReceipt", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity.executor/authored/imported-missing-definition-cannot-prove-retry": { + "purpose": "An imported envelope lacking its saved definition cannot report historical retry success.", + "source": [ + { + "kind": "domain", + "name": "entity.executor" + }, + { + "kind": "type", + "name": "entity.core.JsonDocument" + }, + { + "kind": "type", + "name": "entity.executor.RequestDocument" + }, + { + "kind": "command", + "name": "entity.executor.Create" + }, + { + "kind": "command", + "name": "entity.executor.Register" + }, + { + "kind": "command", + "name": "entity.executor.SeedImported" + }, + { + "kind": "command", + "name": "entity.executor.Tamper" + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Create", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Register", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.SeedImported", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Tamper", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity.executor.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Register", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.SeedImported", + "input": { + "request": { + "kind": "literal", + "value": "{\"history\":{\"origin\":{\"imported\":{\"completeness\":\"available_evidence_only\",\"evidence\":[{\"envelope\":{\"entry\":{\"decision\":{\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"changed\":{\"title\":\"a\"},\"command\":{\"command\":\"create\",\"fields\":{\"title\":\"a\"}},\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1},\"entity\":\"ticket\",\"events\":[],\"from_state\":null,\"id\":\"a\",\"result\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1},\"revision\":1,\"to_state\":\"open\"},\"record_id\":\"historical\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1}}},\"known_order\":{\"per_kind\":0},\"source_id\":\"old\",\"source_locator\":\"ticket/a\"}}],\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1},\"order\":\"per_kind_only\"}},\"records\":[],\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.SeedImported", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.SeedImported", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.SeedImported", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Tamper", + "input": { + "request": { + "kind": "literal", + "value": "{\"field\":\"definition\",\"record_id\":\"historical\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Tamper", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Tamper", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Tamper", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Create", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"historical\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"version\":1}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Create", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "HistoricalRetryUnverifiable", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity.executor/authored/issue-50-batch-refusal-is-atomic": { + "purpose": "A version-bound input refusal leaves an earlier transaction-local creation uncommitted.", + "source": [ + { + "kind": "domain", + "name": "entity.executor" + }, + { + "kind": "type", + "name": "entity.core.JsonDocument" + }, + { + "kind": "type", + "name": "entity.executor.RequestDocument" + }, + { + "kind": "command", + "name": "entity.executor.Batch" + }, + { + "kind": "command", + "name": "entity.executor.Load" + }, + { + "kind": "command", + "name": "entity.executor.LookupRecord" + }, + { + "kind": "command", + "name": "entity.executor.Register" + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Batch", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Load", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Register", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity.executor.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"entity\":\"invoice\",\"version\":1,\"semantics\":\"service/3\",\"schema\":{\"fields\":{}},\"lifecycle\":{\"initial\":\"Open\",\"states\":[\"Open\"]},\"operations\":{\"Pay\":{\"arguments\":{\"fields\":{\"accept\":{\"type\":\"boolean\",\"required\":true}}},\"outcomes\":[{\"name\":\"rejected\",\"when\":{\"eq\":[\"$args.accept\",false]},\"refuses\":{\"error\":\"InputRejected\",\"message\":\"Input was refused\"}},{\"name\":\"paid\",\"effect\":\"updates\"}]}}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Register", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Batch", + "input": { + "request": { + "kind": "literal", + "value": "{\"key\":{\"named\":\"atomic\"},\"actions\":[{\"kind\":\"create\",\"entity\":\"invoice\",\"id\":\"a\",\"version\":1,\"fields\":{},\"recording\":{\"record_id\":\"created\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}},{\"kind\":\"execute\",\"entity\":\"invoice\",\"id\":\"a\",\"definition_version\":1,\"expected_revision\":1,\"operation\":\"Pay\",\"arguments\":{\"accept\":false},\"recording\":{\"record_id\":\"refused\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}]}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Batch", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Batch", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "Refused", + "status": "error", + "value": "{\"detail\":\"outcome 'rejected' refuses with 'InputRejected': Input was refused\"}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Batch", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Load", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"invoice\",\"id\":\"a\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Load", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Load", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "null" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Load", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.LookupRecord", + "input": { + "request": { + "kind": "literal", + "value": "{\"record_id\":\"created\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.LookupRecord", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "null" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.LookupRecord", + "input": { + "request": { + "kind": "literal", + "value": "{\"record_id\":\"refused\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.LookupRecord", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "null" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity.executor/authored/issue-50-malformed-version": { + "purpose": "Present malformed definition versions are rejected instead of silently falling back to legacy execution.", + "source": [ + { + "kind": "domain", + "name": "entity.executor" + }, + { + "kind": "type", + "name": "entity.core.JsonDocument" + }, + { + "kind": "type", + "name": "entity.executor.RequestDocument" + }, + { + "kind": "command", + "name": "entity.executor.Batch" + }, + { + "kind": "command", + "name": "entity.executor.Execute" + }, + { + "kind": "command", + "name": "entity.executor.Trace" + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Batch", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Execute", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Trace", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity.executor.Execute", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"invoice\",\"id\":\"a\",\"definition_version\":null,\"expected_revision\":1,\"operation\":\"Pay\",\"arguments\":{},\"recording\":{\"record_id\":\"bad\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Execute", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Execute", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "Deserialize", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Execute", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Batch", + "input": { + "request": { + "kind": "literal", + "value": "{\"key\":{\"named\":\"bad\"},\"actions\":[{\"kind\":\"execute\",\"entity\":\"invoice\",\"id\":\"a\",\"definition_version\":\"latest\",\"expected_revision\":1,\"operation\":\"Pay\",\"arguments\":{},\"recording\":{\"record_id\":\"bad\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}]}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Batch", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Batch", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "Deserialize", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Batch", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Trace", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Trace", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Trace", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "[]" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Trace", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity.executor/authored/issue-50-versioned-merge-preserves-proof": { + "purpose": "Explicit version binding retains the selected merge base and all branch tips at the real append boundary.", + "source": [ + { + "kind": "domain", + "name": "entity.executor" + }, + { + "kind": "type", + "name": "entity.core.JsonDocument" + }, + { + "kind": "type", + "name": "entity.executor.RequestDocument" + }, + { + "kind": "command", + "name": "entity.executor.Batch" + }, + { + "kind": "command", + "name": "entity.executor.CapturedMerge" + }, + { + "kind": "command", + "name": "entity.executor.LookupRecord" + }, + { + "kind": "command", + "name": "entity.executor.Register" + }, + { + "kind": "command", + "name": "entity.executor.SupplyHistory" + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Batch", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.CapturedMerge", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Register", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.SupplyHistory", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity.executor.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Register", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.SupplyHistory", + "input": { + "request": { + "kind": "literal", + "value": "{\"history\":{\"origin\":\"genesis\",\"records\":[{\"entry\":{\"decision\":{\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"changed\":{\"title\":\"a\"},\"command\":{\"command\":\"create\",\"fields\":{\"title\":\"a\"}},\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1},\"entity\":\"ticket\",\"events\":[],\"from_state\":null,\"id\":\"a\",\"result\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1},\"revision\":1,\"to_state\":\"open\"},\"record_id\":\"root\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":1,\"version\":1}}},\"expect\":null,\"lineage\":{\"digest\":\"root\",\"parents\":[]},\"position\":{\"store\":0,\"subject\":0},\"receipt\":{\"batch_key\":{\"single_record\":\"root\"},\"kind\":\"decision\",\"member_index\":0,\"position\":{\"store\":0,\"subject\":0},\"record_id\":\"root\",\"revision\":1,\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}},{\"entry\":{\"decision\":{\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"changed\":{},\"command\":{\"arguments\":{},\"command\":\"execute\",\"operation\":\"touch\"},\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1},\"entity\":\"ticket\",\"events\":[],\"from_state\":\"open\",\"id\":\"a\",\"result\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":2,\"version\":1},\"revision\":2,\"to_state\":\"open\"},\"record_id\":\"left\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":2,\"version\":1}}},\"expect\":1,\"lineage\":{\"digest\":\"left\",\"parents\":[\"root\"]},\"position\":{\"store\":1,\"subject\":1},\"receipt\":{\"batch_key\":{\"single_record\":\"left\"},\"kind\":\"decision\",\"member_index\":0,\"position\":{\"store\":1,\"subject\":1},\"record_id\":\"left\",\"revision\":2,\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}},{\"entry\":{\"decision\":{\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"changed\":{},\"command\":{\"arguments\":{},\"command\":\"execute\",\"operation\":\"close\"},\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1},\"entity\":\"ticket\",\"events\":[],\"from_state\":\"open\",\"id\":\"a\",\"result\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"closed\",\"revision\":2,\"version\":1},\"revision\":2,\"to_state\":\"closed\"},\"record_id\":\"right\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"instance\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"closed\",\"revision\":2,\"version\":1}}},\"expect\":1,\"lineage\":{\"digest\":\"right\",\"parents\":[\"root\"]},\"position\":{\"store\":2,\"subject\":2},\"receipt\":{\"batch_key\":{\"single_record\":\"right\"},\"kind\":\"decision\",\"member_index\":0,\"position\":{\"store\":2,\"subject\":2},\"record_id\":\"right\",\"revision\":2,\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}}],\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.SupplyHistory", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.SupplyHistory", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.SupplyHistory", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Batch", + "input": { + "request": { + "kind": "literal", + "value": "{\"actions\":[{\"arguments\":{},\"entity\":\"ticket\",\"expected_revision\":2,\"first\":\"left\",\"id\":\"a\",\"kind\":\"merge\",\"definition_version\":1,\"operation\":\"touch\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"merge\",\"recorded_at\":\"2026-09-28T00:00:00Z\"}}],\"key\":{\"single_record\":\"merge\"}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Batch", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Batch", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "Backend", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Batch", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.CapturedMerge", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.CapturedMerge", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.CapturedMerge", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok", + "value": "[{\"merge\":{\"base\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":2,\"version\":1},\"heads\":[\"left\",\"right\"]},\"result\":{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"lifecycle_state\":\"open\",\"revision\":3,\"version\":1}}]" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.CapturedMerge", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.LookupRecord", + "input": { + "request": { + "kind": "literal", + "value": "{\"record_id\":\"merge\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.LookupRecord", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok", + "value": "null" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + } + }, + "step": "expect_direct_response" } ] }, @@ -57948,8 +59613,297 @@ } }, "expected": { - "error": "Forked", - "status": "error" + "error": "Forked", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Execute", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.RefusalReasons", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.RefusalReasons", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.RefusalReasons", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok", + "value": "[{\"heads\":[\"left\",\"right\"],\"kind\":\"Forked\",\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}]" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.RefusalReasons", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity.executor/authored/refusal-ignores-success-fulfillments": { + "purpose": "An input guard preserves its declared refusal despite success fulfillment keys, without recording a decision.", + "source": [ + { + "kind": "domain", + "name": "entity.executor" + }, + { + "kind": "type", + "name": "entity.core.JsonDocument" + }, + { + "kind": "type", + "name": "entity.executor.RequestDocument" + }, + { + "kind": "command", + "name": "entity.executor.Create" + }, + { + "kind": "command", + "name": "entity.executor.Execute" + }, + { + "kind": "command", + "name": "entity.executor.HistoryCoordinates" + }, + { + "kind": "command", + "name": "entity.executor.Load" + }, + { + "kind": "command", + "name": "entity.executor.LookupReceipt" + }, + { + "kind": "command", + "name": "entity.executor.Register" + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Create", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Execute", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.HistoryCoordinates", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Load", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.LookupReceipt", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Register", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity.executor.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"entity\":\"invoice\",\"version\":1,\"semantics\":\"service/3\",\"schema\":{\"fields\":{\"issued_at\":{\"type\":\"string\",\"required\":true},\"note\":{\"type\":\"string\"}}},\"lifecycle\":{\"initial\":\"Draft\",\"states\":[\"Draft\"]},\"operations\":{\"Issue\":{\"arguments\":{\"fields\":{\"accept\":{\"type\":\"boolean\",\"required\":true}}},\"outcomes\":[{\"name\":\"rejected\",\"when\":{\"eq\":[\"$args.accept\",false]},\"refuses\":{\"error\":\"NotAccepted\",\"message\":\"Invoice cannot be issued\"}},{\"name\":\"issued\",\"effect\":\"updates\",\"fulfills\":{\"issued_at\":{\"actions\":\"required\"},\"note\":{\"actions\":\"optional\"}}}]}}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Register", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Create", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"invoice\",\"id\":\"i-1\",\"version\":1,\"fields\":{\"issued_at\":\"pending\",\"note\":\"remove\"},\"recording\":{\"record_id\":\"r-1\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"correlation\":null,\"causation\":null,\"actor\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Create", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Execute", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"invoice\",\"id\":\"i-1\",\"expected_revision\":1,\"operation\":\"Issue\",\"arguments\":{\"accept\":false},\"fulfillments\":{\"issued_at\":{\"set\":{\"value\":\"must not be stored\"}},\"note\":\"remove\"},\"recording\":{\"record_id\":\"r-2\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"correlation\":null,\"causation\":null,\"actor\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Execute", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Execute", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "Refused", + "status": "error", + "value": "{\"detail\":\"outcome 'rejected' refuses with 'NotAccepted': Invoice cannot be issued\"}" }, "fields": [ { @@ -57973,25 +59927,25 @@ "step": "expect_direct_response" }, { - "command": "entity.executor.RefusalReasons", + "command": "entity.executor.Load", "input": { "request": { "kind": "literal", - "value": "{}" + "value": "{\"entity\":\"invoice\",\"id\":\"i-1\"}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.RefusalReasons", + "command": "entity.executor.Load", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.RefusalReasons", + "command": "entity.executor.Load", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -57999,9 +59953,8 @@ } }, "expected": { - "error": "", "status": "ok", - "value": "[{\"heads\":[\"left\",\"right\"],\"kind\":\"Forked\",\"subject\":{\"entity\":\"ticket\",\"id\":\"a\"}}]" + "value": "{\"entity\":\"invoice\",\"fields\":{\"issued_at\":\"pending\",\"note\":\"remove\"},\"id\":\"i-1\",\"lifecycle_state\":\"Draft\",\"revision\":1,\"version\":1}" }, "fields": [ { @@ -58018,7 +59971,109 @@ } ], "outcome": { - "command": "entity.executor.RefusalReasons", + "command": "entity.executor.Load", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.LookupReceipt", + "input": { + "request": { + "kind": "literal", + "value": "{\"record_id\":\"r-2\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.LookupReceipt", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.LookupReceipt", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "null" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.LookupReceipt", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.HistoryCoordinates", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"invoice\",\"id\":\"i-1\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.HistoryCoordinates", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.HistoryCoordinates", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "[{\"events\":[],\"kind\":\"decision\",\"receipt\":{\"batch_key\":{\"single_record\":\"r-1\"},\"kind\":\"decision\",\"member_index\":0,\"position\":{\"store\":0,\"subject\":0},\"record_id\":\"r-1\",\"revision\":1,\"subject\":{\"entity\":\"invoice\",\"id\":\"i-1\"}},\"record_id\":\"r-1\",\"revision\":1}]" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.HistoryCoordinates", "outcome": "returned" } }, @@ -59640,25 +61695,177 @@ "step": "expect_direct_response" }, { - "command": "entity.executor.Load", + "command": "entity.executor.Load", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"invoice\",\"id\":\"i-1\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Load", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Load", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"entity\":\"invoice\",\"fields\":{\"issued_at\":\"issued\"},\"id\":\"i-1\",\"lifecycle_state\":\"Draft\",\"revision\":2,\"version\":1}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Load", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.ClearRegistry", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.ClearRegistry", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.ClearRegistry", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.ClearRegistry", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Execute", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"invoice\",\"id\":\"i-1\",\"expected_revision\":1,\"operation\":\"Issue\",\"arguments\":{},\"fulfillments\":{\"issued_at\":{\"set\":{\"value\":\"issued\"}},\"note\":\"remove\"},\"recording\":{\"record_id\":\"r-2\",\"recorded_at\":\"2026-09-28T00:00:00Z\",\"correlation\":null,\"causation\":null,\"actor\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Execute", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Execute", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"kind\":\"Committed\",\"receipt\":{\"single\":{\"batch_key\":{\"single_record\":\"r-2\"},\"kind\":\"decision\",\"member_index\":0,\"position\":{\"store\":1,\"subject\":1},\"record_id\":\"r-2\",\"revision\":2,\"subject\":{\"entity\":\"invoice\",\"id\":\"i-1\"}}},\"replayed\":true}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Execute", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Execute", "input": { "request": { "kind": "literal", - "value": "{\"entity\":\"invoice\",\"id\":\"i-1\"}" + "value": "{\"entity\":\"invoice\",\"id\":\"i-1\",\"expected_revision\":1,\"operation\":\"Issue\",\"arguments\":{},\"fulfillments\":{\"issued_at\":{\"set\":{\"value\":\"different\"}},\"note\":\"remove\"},\"recording\":{\"record_id\":\"r-2\",\"recorded_at\":\"2026-09-28T00:00:00Z\",\"correlation\":null,\"causation\":null,\"actor\":null}}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.Load", + "command": "entity.executor.Execute", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.Load", + "command": "entity.executor.Execute", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -59666,8 +61873,8 @@ } }, "expected": { - "status": "ok", - "value": "{\"entity\":\"invoice\",\"fields\":{\"issued_at\":\"issued\"},\"id\":\"i-1\",\"lifecycle_state\":\"Draft\",\"revision\":2,\"version\":1}" + "error": "RecordConflict", + "status": "error" }, "fields": [ { @@ -59684,32 +61891,32 @@ } ], "outcome": { - "command": "entity.executor.Load", + "command": "entity.executor.Execute", "outcome": "returned" } }, "step": "expect_direct_response" }, { - "command": "entity.executor.ClearRegistry", + "command": "entity.executor.VerifyComplete", "input": { "request": { "kind": "literal", - "value": "{}" + "value": "{\"scope\":\"service-3\"}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.ClearRegistry", + "command": "entity.executor.VerifyComplete", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.ClearRegistry", + "command": "entity.executor.VerifyComplete", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -59717,7 +61924,8 @@ } }, "expected": { - "status": "ok" + "status": "ok", + "value": "{\"coverage\":\"CompleteSnapshot\",\"scope\":\"service-3\",\"subjects\":[{\"kind\":\"VerifiedFromGenesis\",\"subject\":{\"entity\":\"invoice\",\"id\":\"i-1\"}}]}" }, "fields": [ { @@ -59734,32 +61942,84 @@ } ], "outcome": { - "command": "entity.executor.ClearRegistry", + "command": "entity.executor.VerifyComplete", "outcome": "returned" } }, "step": "expect_direct_response" + } + ] + }, + "entity.executor/authored/single-key-must-match-member": { + "purpose": "Single-record keys require exactly one action with the same record ID.", + "source": [ + { + "kind": "domain", + "name": "entity.executor" }, { - "command": "entity.executor.Execute", + "kind": "type", + "name": "entity.core.JsonDocument" + }, + { + "kind": "type", + "name": "entity.executor.RequestDocument" + }, + { + "kind": "command", + "name": "entity.executor.Batch" + }, + { + "kind": "command", + "name": "entity.executor.Register" + }, + { + "kind": "command", + "name": "entity.executor.Trace" + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Batch", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Register", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Trace", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity.executor.Register", "input": { "request": { "kind": "literal", - "value": "{\"entity\":\"invoice\",\"id\":\"i-1\",\"expected_revision\":1,\"operation\":\"Issue\",\"arguments\":{},\"fulfillments\":{\"issued_at\":{\"set\":{\"value\":\"issued\"}},\"note\":\"remove\"},\"recording\":{\"record_id\":\"r-2\",\"recorded_at\":\"2026-09-28T00:00:00Z\",\"correlation\":null,\"causation\":null,\"actor\":null}}" + "value": "{\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1}}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.Execute", + "command": "entity.executor.Register", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.Execute", + "command": "entity.executor.Register", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -59767,8 +62027,8 @@ } }, "expected": { - "status": "ok", - "value": "{\"kind\":\"Committed\",\"receipt\":{\"single\":{\"batch_key\":{\"single_record\":\"r-2\"},\"kind\":\"decision\",\"member_index\":0,\"position\":{\"store\":1,\"subject\":1},\"record_id\":\"r-2\",\"revision\":2,\"subject\":{\"entity\":\"invoice\",\"id\":\"i-1\"}}},\"replayed\":true}" + "error": "", + "status": "ok" }, "fields": [ { @@ -59785,32 +62045,32 @@ } ], "outcome": { - "command": "entity.executor.Execute", + "command": "entity.executor.Register", "outcome": "returned" } }, "step": "expect_direct_response" }, { - "command": "entity.executor.Execute", + "command": "entity.executor.Batch", "input": { "request": { "kind": "literal", - "value": "{\"entity\":\"invoice\",\"id\":\"i-1\",\"expected_revision\":1,\"operation\":\"Issue\",\"arguments\":{},\"fulfillments\":{\"issued_at\":{\"set\":{\"value\":\"different\"}},\"note\":\"remove\"},\"recording\":{\"record_id\":\"r-2\",\"recorded_at\":\"2026-09-28T00:00:00Z\",\"correlation\":null,\"causation\":null,\"actor\":null}}" + "value": "{\"actions\":[{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"kind\":\"create\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"one\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"version\":1}],\"key\":{\"single_record\":\"other\"}}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.Execute", + "command": "entity.executor.Batch", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.Execute", + "command": "entity.executor.Batch", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -59818,7 +62078,7 @@ } }, "expected": { - "error": "RecordConflict", + "error": "InvalidInput", "status": "error" }, "fields": [ @@ -59836,32 +62096,32 @@ } ], "outcome": { - "command": "entity.executor.Execute", + "command": "entity.executor.Batch", "outcome": "returned" } }, "step": "expect_direct_response" }, { - "command": "entity.executor.VerifyComplete", + "command": "entity.executor.Trace", "input": { "request": { "kind": "literal", - "value": "{\"scope\":\"service-3\"}" + "value": "{}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.VerifyComplete", + "command": "entity.executor.Trace", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.VerifyComplete", + "command": "entity.executor.Trace", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -59869,8 +62129,9 @@ } }, "expected": { + "error": "", "status": "ok", - "value": "{\"coverage\":\"CompleteSnapshot\",\"scope\":\"service-3\",\"subjects\":[{\"kind\":\"VerifiedFromGenesis\",\"subject\":{\"entity\":\"invoice\",\"id\":\"i-1\"}}]}" + "value": "[]" }, "fields": [ { @@ -59887,7 +62148,7 @@ } ], "outcome": { - "command": "entity.executor.VerifyComplete", + "command": "entity.executor.Trace", "outcome": "returned" } }, @@ -59895,8 +62156,8 @@ } ] }, - "entity.executor/authored/single-key-must-match-member": { - "purpose": "Single-record keys require exactly one action with the same record ID.", + "entity.executor/authored/stale-observation-rolls-back-whole-batch": { + "purpose": "A stale later observation prevents every decision, identity and receipt in the batch.", "source": [ { "kind": "domain", @@ -59916,11 +62177,19 @@ }, { "kind": "command", - "name": "entity.executor.Register" + "name": "entity.executor.HistoryCoordinates" }, { "kind": "command", - "name": "entity.executor.Trace" + "name": "entity.executor.Load" + }, + { + "kind": "command", + "name": "entity.executor.LookupRecord" + }, + { + "kind": "command", + "name": "entity.executor.Register" }, { "kind": "outcome", @@ -59932,14 +62201,28 @@ { "kind": "outcome", "name": { - "command": "entity.executor.Register", + "command": "entity.executor.HistoryCoordinates", "outcome": "returned" } }, { "kind": "outcome", "name": { - "command": "entity.executor.Trace", + "command": "entity.executor.Load", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Register", "outcome": "returned" } } @@ -60001,7 +62284,7 @@ "input": { "request": { "kind": "literal", - "value": "{\"actions\":[{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"kind\":\"create\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"one\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"version\":1}],\"key\":{\"single_record\":\"other\"}}" + "value": "{\"actions\":[{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"kind\":\"create\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"one\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"version\":1},{\"arguments\":{},\"entity\":\"ticket\",\"expected_revision\":1,\"id\":\"a\",\"kind\":\"execute\",\"operation\":\"touch\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"two\",\"recorded_at\":\"2026-09-28T00:00:00Z\"}},{\"kind\":\"observe\",\"observation\":{\"entity\":\"ticket\",\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"evidence\":null},\"record_id\":\"stale\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"id\":\"a\",\"revision\":1}}],\"key\":{\"named\":\"mixed\"}}" } }, "step": "execute_command" @@ -60023,7 +62306,7 @@ } }, "expected": { - "error": "InvalidInput", + "error": "RevisionConflict", "status": "error" }, "fields": [ @@ -60048,25 +62331,129 @@ "step": "expect_direct_response" }, { - "command": "entity.executor.Trace", + "command": "entity.executor.Load", "input": { "request": { "kind": "literal", - "value": "{}" + "value": "{\"entity\":\"ticket\",\"id\":\"a\"}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.Trace", + "command": "entity.executor.Load", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.Trace", + "command": "entity.executor.Load", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok", + "value": "null" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Load", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.LookupRecord", + "input": { + "request": { + "kind": "literal", + "value": "{\"record_id\":\"one\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.LookupRecord", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "", + "status": "ok", + "value": "null" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.LookupRecord", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.HistoryCoordinates", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.HistoryCoordinates", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.HistoryCoordinates", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -60093,7 +62480,7 @@ } ], "outcome": { - "command": "entity.executor.Trace", + "command": "entity.executor.HistoryCoordinates", "outcome": "returned" } }, @@ -60101,8 +62488,8 @@ } ] }, - "entity.executor/authored/stale-observation-rolls-back-whole-batch": { - "purpose": "A stale later observation prevents every decision, identity and receipt in the batch.", + "entity.executor/authored/subject-refusal-ignores-success-fulfillments": { + "purpose": "A stored-field guard preserves its declared refusal despite success fulfillment keys, without recording a decision.", "source": [ { "kind": "domain", @@ -60118,7 +62505,11 @@ }, { "kind": "command", - "name": "entity.executor.Batch" + "name": "entity.executor.Create" + }, + { + "kind": "command", + "name": "entity.executor.Execute" }, { "kind": "command", @@ -60130,7 +62521,7 @@ }, { "kind": "command", - "name": "entity.executor.LookupRecord" + "name": "entity.executor.LookupReceipt" }, { "kind": "command", @@ -60139,7 +62530,14 @@ { "kind": "outcome", "name": { - "command": "entity.executor.Batch", + "command": "entity.executor.Create", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity.executor.Execute", "outcome": "returned" } }, @@ -60160,7 +62558,7 @@ { "kind": "outcome", "name": { - "command": "entity.executor.LookupRecord", + "command": "entity.executor.LookupReceipt", "outcome": "returned" } }, @@ -60178,7 +62576,7 @@ "input": { "request": { "kind": "literal", - "value": "{\"definition\":{\"create\":{\"emit\":null},\"entity\":\"ticket\",\"invariants\":[],\"lifecycle\":{\"initial\":\"open\",\"states\":[\"open\",\"closed\"]},\"operations\":{\"close\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"closed\"}]},\"touch\":{\"arguments\":{\"additional_fields\":false,\"fields\":{}},\"emits\":[],\"preconditions\":[],\"set\":{},\"transitions\":[{\"from\":\"open\",\"to\":\"open\"}]}},\"projections\":{},\"schema\":{\"additional_fields\":false,\"fields\":{\"title\":{\"acyclic\":null,\"additional_properties\":false,\"entity\":null,\"inverse\":null,\"items\":null,\"max\":null,\"max_length\":null,\"min\":null,\"min_length\":null,\"properties\":{},\"required\":true,\"type\":\"string\",\"values\":[]}}},\"version\":1}}" + "value": "{\"definition\":{\"entity\":\"invoice\",\"version\":1,\"semantics\":\"service/3\",\"schema\":{\"fields\":{\"issued_at\":{\"type\":\"string\",\"required\":true},\"note\":{\"type\":\"string\"}}},\"lifecycle\":{\"initial\":\"Draft\",\"states\":[\"Draft\"]},\"operations\":{\"Issue\":{\"arguments\":{\"fields\":{\"accept\":{\"type\":\"boolean\",\"required\":true}}},\"outcomes\":[{\"name\":\"rejected\",\"when\":{\"eq\":[\"$fields.issued_at\",\"pending\"]},\"refuses\":{\"error\":\"NotAccepted\",\"message\":\"Invoice cannot be issued\"}},{\"name\":\"issued\",\"effect\":\"updates\",\"fulfills\":{\"issued_at\":{\"actions\":\"required\"},\"note\":{\"actions\":\"optional\"}}}]}}}}" } }, "step": "execute_command" @@ -60200,7 +62598,6 @@ } }, "expected": { - "error": "", "status": "ok" }, "fields": [ @@ -60225,25 +62622,25 @@ "step": "expect_direct_response" }, { - "command": "entity.executor.Batch", + "command": "entity.executor.Create", "input": { "request": { "kind": "literal", - "value": "{\"actions\":[{\"entity\":\"ticket\",\"fields\":{\"title\":\"a\"},\"id\":\"a\",\"kind\":\"create\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"one\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"version\":1},{\"arguments\":{},\"entity\":\"ticket\",\"expected_revision\":1,\"id\":\"a\",\"kind\":\"execute\",\"operation\":\"touch\",\"recording\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record_id\":\"two\",\"recorded_at\":\"2026-09-28T00:00:00Z\"}},{\"kind\":\"observe\",\"observation\":{\"entity\":\"ticket\",\"envelope\":{\"actor\":null,\"causation\":null,\"correlation\":null,\"record\":{\"evidence\":null},\"record_id\":\"stale\",\"recorded_at\":\"2026-09-28T00:00:00Z\"},\"id\":\"a\",\"revision\":1}}],\"key\":{\"named\":\"mixed\"}}" + "value": "{\"entity\":\"invoice\",\"id\":\"i-1\",\"version\":1,\"fields\":{\"issued_at\":\"pending\",\"note\":\"remove\"},\"recording\":{\"record_id\":\"r-1\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"correlation\":null,\"causation\":null,\"actor\":null}}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.Batch", + "command": "entity.executor.Create", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.Batch", + "command": "entity.executor.Create", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -60251,8 +62648,7 @@ } }, "expected": { - "error": "RevisionConflict", - "status": "error" + "status": "ok" }, "fields": [ { @@ -60269,7 +62665,59 @@ } ], "outcome": { - "command": "entity.executor.Batch", + "command": "entity.executor.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity.executor.Execute", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"invoice\",\"id\":\"i-1\",\"expected_revision\":1,\"operation\":\"Issue\",\"arguments\":{\"accept\":false},\"fulfillments\":{\"issued_at\":{\"set\":{\"value\":\"must not be stored\"}},\"note\":\"remove\"},\"recording\":{\"record_id\":\"r-2\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"correlation\":null,\"causation\":null,\"actor\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity.executor.Execute", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity.executor.Execute", + "declarations": { + "entity.core.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "Refused", + "status": "error", + "value": "{\"detail\":\"outcome 'rejected' refuses with 'NotAccepted': Invoice cannot be issued\"}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity.core.JsonDocument" + } + ], + "outcome": { + "command": "entity.executor.Execute", "outcome": "returned" } }, @@ -60280,7 +62728,7 @@ "input": { "request": { "kind": "literal", - "value": "{\"entity\":\"ticket\",\"id\":\"a\"}" + "value": "{\"entity\":\"invoice\",\"id\":\"i-1\"}" } }, "step": "execute_command" @@ -60302,9 +62750,8 @@ } }, "expected": { - "error": "", "status": "ok", - "value": "null" + "value": "{\"entity\":\"invoice\",\"fields\":{\"issued_at\":\"pending\",\"note\":\"remove\"},\"id\":\"i-1\",\"lifecycle_state\":\"Draft\",\"revision\":1,\"version\":1}" }, "fields": [ { @@ -60328,25 +62775,25 @@ "step": "expect_direct_response" }, { - "command": "entity.executor.LookupRecord", + "command": "entity.executor.LookupReceipt", "input": { "request": { "kind": "literal", - "value": "{\"record_id\":\"one\"}" + "value": "{\"record_id\":\"r-2\"}" } }, "step": "execute_command" }, { "outcome": { - "command": "entity.executor.LookupRecord", + "command": "entity.executor.LookupReceipt", "outcome": "returned" }, "step": "expect_outcome" }, { "response": { - "command": "entity.executor.LookupRecord", + "command": "entity.executor.LookupReceipt", "declarations": { "entity.core.JsonDocument": { "kind": "newtype", @@ -60354,7 +62801,6 @@ } }, "expected": { - "error": "", "status": "ok", "value": "null" }, @@ -60373,7 +62819,7 @@ } ], "outcome": { - "command": "entity.executor.LookupRecord", + "command": "entity.executor.LookupReceipt", "outcome": "returned" } }, @@ -60384,7 +62830,7 @@ "input": { "request": { "kind": "literal", - "value": "{\"entity\":\"ticket\",\"id\":\"a\"}" + "value": "{\"entity\":\"invoice\",\"id\":\"i-1\"}" } }, "step": "execute_command" @@ -60406,9 +62852,8 @@ } }, "expected": { - "error": "", "status": "ok", - "value": "[]" + "value": "[{\"events\":[],\"kind\":\"decision\",\"receipt\":{\"batch_key\":{\"single_record\":\"r-1\"},\"kind\":\"decision\",\"member_index\":0,\"position\":{\"store\":0,\"subject\":0},\"record_id\":\"r-1\",\"revision\":1,\"subject\":{\"entity\":\"invoice\",\"id\":\"i-1\"}},\"record_id\":\"r-1\",\"revision\":1}]" }, "fields": [ { diff --git a/ess/provider-tracking/coverage.json b/ess/provider-tracking/coverage.json new file mode 100644 index 00000000..cc89a916 --- /dev/null +++ b/ess/provider-tracking/coverage.json @@ -0,0 +1,42 @@ +{ + "format": "er-ess-coverage/2", + "review": "Issue 51: admit seven real SQLite and public facade scenarios for exact state/history semantics and five SQL corruptions with unchanged event heads; retain the original 421-scenario contract unchanged.", + "scenarios": [ + "entity-provider.tracking.Batch/outcome/returned", + "entity-provider.tracking.Create/outcome/returned", + "entity-provider.tracking.Histories/outcome/returned", + "entity-provider.tracking.Load/outcome/returned", + "entity-provider.tracking.LookupBatch/outcome/returned", + "entity-provider.tracking.Provision/outcome/returned", + "entity-provider.tracking.Register/outcome/returned", + "entity-provider.tracking.Reopen/outcome/returned", + "entity-provider.tracking.Snapshot/outcome/returned", + "entity-provider.tracking.SqlMutate/outcome/returned", + "entity-provider.tracking/authored/multi-subject-histories-one-scope", + "entity-provider.tracking/authored/unchanged-capture-retains-verified-state", + "entity-provider.tracking/authored/unchanged-head-blob-tamper", + "entity-provider.tracking/authored/unchanged-head-delete-blob-tamper", + "entity-provider.tracking/authored/unchanged-head-event-tamper", + "entity-provider.tracking/authored/unchanged-head-identity-tamper", + "entity-provider.tracking/authored/unchanged-head-projection-tamper" + ], + "contracts": { + "entity-provider.tracking.Batch/outcome/returned": "sha256:22607c1c5675c479fafeba66d96e76a0262864626b798f5b46095f6f0a8cce93", + "entity-provider.tracking.Create/outcome/returned": "sha256:0c06bfff5e0e6942c5f51b649ce03d4fafe76edc6c7cbe43d4e5e4867ad4b660", + "entity-provider.tracking.Histories/outcome/returned": "sha256:201760fbb6f57fb2d56013ae856612a6e73855f802239d8443e6a4d8a00d59a6", + "entity-provider.tracking.Load/outcome/returned": "sha256:a5fed80769d30c356904f047e643080a78b7c9aaf1530e2288314af4617cc072", + "entity-provider.tracking.LookupBatch/outcome/returned": "sha256:803e7b91ec6a5522e117fc0832c6164a2714e972de417a5f47eb8312e09511ce", + "entity-provider.tracking.Provision/outcome/returned": "sha256:128b0d3d83bf0b4612f3e3a983c55aa7771f5861c255bf55971ad8ef7d6aafb1", + "entity-provider.tracking.Register/outcome/returned": "sha256:d73ccb4dca73f0fa8eda70a375109d5bba8739eaba7f04795e767df20fd5224f", + "entity-provider.tracking.Reopen/outcome/returned": "sha256:939ba7b4baf4cc782d7905a42c9cceb9a9d3c516cdd34cf800972c2dc4ab0a48", + "entity-provider.tracking.Snapshot/outcome/returned": "sha256:c21d9a434cc9b54618fab793f7c592a2de727ce4b7b5ff2eaf7d9f513f7887dc", + "entity-provider.tracking.SqlMutate/outcome/returned": "sha256:809bae3a1ba4f0c3d680605645cdc9e15c63fb2a4f574343dd6bcfd086436c15", + "entity-provider.tracking/authored/multi-subject-histories-one-scope": "sha256:e4232edce8bd789c1302dc9ad52f7e289dbf14224d9c3b9cb20443f91aebf224", + "entity-provider.tracking/authored/unchanged-capture-retains-verified-state": "sha256:689fbf50253b167579ab9e66427506ab41b2e50b241bd6f95d225407dcc61d22", + "entity-provider.tracking/authored/unchanged-head-blob-tamper": "sha256:a8fc8cf0f81ba50923241f9d57732b426573ea6b35a95f11d6034fb136c43dbf", + "entity-provider.tracking/authored/unchanged-head-delete-blob-tamper": "sha256:37ecf56441bf8be29f0c0d909e775f5b65a74ed6e76c5a6ab9641830bc4408a9", + "entity-provider.tracking/authored/unchanged-head-event-tamper": "sha256:f21024400fea76ba02a9a2968c9ab83a761af0e50655613fc71b063f5f85c0f3", + "entity-provider.tracking/authored/unchanged-head-identity-tamper": "sha256:d3e4c67f158a4e6de73adc0a81c0000397fe4b2d1898338e47f3714c51456263", + "entity-provider.tracking/authored/unchanged-head-projection-tamper": "sha256:f149b22ad9c1ec2350bc95583466c228593efb9843b1bfe4910f756c4d75a0c8" + } +} diff --git a/ess/provider-tracking/domains/operations.yaml b/ess/provider-tracking/domains/operations.yaml new file mode 100644 index 00000000..5d7d3216 --- /dev/null +++ b/ess/provider-tracking/domains/operations.yaml @@ -0,0 +1,94 @@ +domain: entity-provider.tracking +types: + - {name: entity-provider.tracking.JsonDocument, kind: newtype, of: String} +commands: + - name: entity-provider.tracking.Register + input: + - {name: request, type: entity-provider.tracking.JsonDocument} + response: + - {name: status, type: String} + - {name: error, type: String} + - {name: value, type: entity-provider.tracking.JsonDocument} + outcomes: + - {name: returned, returns: true} + - name: entity-provider.tracking.Provision + input: + - {name: request, type: entity-provider.tracking.JsonDocument} + response: + - {name: status, type: String} + - {name: error, type: String} + - {name: value, type: entity-provider.tracking.JsonDocument} + outcomes: + - {name: returned, returns: true} + - name: entity-provider.tracking.Reopen + input: + - {name: request, type: entity-provider.tracking.JsonDocument} + response: + - {name: status, type: String} + - {name: error, type: String} + - {name: value, type: entity-provider.tracking.JsonDocument} + outcomes: + - {name: returned, returns: true} + - name: entity-provider.tracking.Create + input: + - {name: request, type: entity-provider.tracking.JsonDocument} + response: + - {name: status, type: String} + - {name: error, type: String} + - {name: value, type: entity-provider.tracking.JsonDocument} + outcomes: + - {name: returned, returns: true} + - name: entity-provider.tracking.Batch + input: + - {name: request, type: entity-provider.tracking.JsonDocument} + response: + - {name: status, type: String} + - {name: error, type: String} + - {name: value, type: entity-provider.tracking.JsonDocument} + outcomes: + - {name: returned, returns: true} + - name: entity-provider.tracking.Load + input: + - {name: request, type: entity-provider.tracking.JsonDocument} + response: + - {name: status, type: String} + - {name: error, type: String} + - {name: value, type: entity-provider.tracking.JsonDocument} + outcomes: + - {name: returned, returns: true} + - name: entity-provider.tracking.Histories + input: + - {name: request, type: entity-provider.tracking.JsonDocument} + response: + - {name: status, type: String} + - {name: error, type: String} + - {name: value, type: entity-provider.tracking.JsonDocument} + outcomes: + - {name: returned, returns: true} + - name: entity-provider.tracking.LookupBatch + input: + - {name: request, type: entity-provider.tracking.JsonDocument} + response: + - {name: status, type: String} + - {name: error, type: String} + - {name: value, type: entity-provider.tracking.JsonDocument} + outcomes: + - {name: returned, returns: true} + - name: entity-provider.tracking.Snapshot + input: + - {name: request, type: entity-provider.tracking.JsonDocument} + response: + - {name: status, type: String} + - {name: error, type: String} + - {name: value, type: entity-provider.tracking.JsonDocument} + outcomes: + - {name: returned, returns: true} + - name: entity-provider.tracking.SqlMutate + input: + - {name: request, type: entity-provider.tracking.JsonDocument} + response: + - {name: status, type: String} + - {name: error, type: String} + - {name: value, type: entity-provider.tracking.JsonDocument} + outcomes: + - {name: returned, returns: true} diff --git a/ess/provider-tracking/domains/provider.yaml b/ess/provider-tracking/domains/provider.yaml new file mode 100644 index 00000000..f110a756 --- /dev/null +++ b/ess/provider-tracking/domains/provider.yaml @@ -0,0 +1,16 @@ +domain: entity-provider.tracking +summary: Explicit transient read policy for an Eventlog-backed Entity Runtime handle. + +# This policy belongs to a live reader. It is never persisted in authority events, +# stored decisions, requests, receipts, or provider bindings. +# FullVerification is the existing constructors' default: every complete read +# obtains fresh provider content. ProviderTracked is an explicit opt-in after a +# complete verification on open; a supported provider may attest to unchanged +# SQL-visible content or an exact append suffix. Unsupported or invalidated +# attestations fall back to complete verification. SQLite tracking covers SQL +# writes through any connection; raw file edits bypassing SQLite are outside +# the warm tracking guarantee and are checked when the handle opens again. +types: + - name: entity-provider.tracking.CapturePolicy + kind: enum + variants: [FullVerification, ProviderTracked] diff --git a/ess/provider-tracking/ess-inputs.yaml b/ess/provider-tracking/ess-inputs.yaml new file mode 100644 index 00000000..2abaee0b --- /dev/null +++ b/ess/provider-tracking/ess-inputs.yaml @@ -0,0 +1,13 @@ +format: ess-inputs/1 +specification: + - system.yaml + - domains/provider.yaml + - domains/operations.yaml +scenarios: + - scenarios/multi-subject-histories-one-scope.yaml + - scenarios/unchanged-capture-retains-verified-state.yaml + - scenarios/unchanged-head-blob-tamper.yaml + - scenarios/unchanged-head-delete-blob-tamper.yaml + - scenarios/unchanged-head-event-tamper.yaml + - scenarios/unchanged-head-identity-tamper.yaml + - scenarios/unchanged-head-projection-tamper.yaml diff --git a/ess/provider-tracking/generated/model.json b/ess/provider-tracking/generated/model.json new file mode 100644 index 00000000..d84fa56c --- /dev/null +++ b/ess/provider-tracking/generated/model.json @@ -0,0 +1,590 @@ +{ + "system": "entity-provider", + "version": 1, + "naming": {}, + "domains": { + "entity-provider.tracking": { + "name": "entity-provider.tracking", + "naming": { + "summary": "Explicit transient read policy for an Eventlog-backed Entity Runtime handle." + }, + "types": [ + "entity-provider.tracking.CapturePolicy", + "entity-provider.tracking.JsonDocument" + ], + "entities": [], + "commands": [ + "entity-provider.tracking.Batch", + "entity-provider.tracking.Create", + "entity-provider.tracking.Histories", + "entity-provider.tracking.Load", + "entity-provider.tracking.LookupBatch", + "entity-provider.tracking.Provision", + "entity-provider.tracking.Register", + "entity-provider.tracking.Reopen", + "entity-provider.tracking.Snapshot", + "entity-provider.tracking.SqlMutate" + ], + "events": [], + "errors": [], + "views": [], + "actors": [] + } + }, + "types": { + "entity-provider.tracking.CapturePolicy": { + "name": "entity-provider.tracking.CapturePolicy", + "body": { + "kind": "enum", + "variants": [ + "FullVerification", + "ProviderTracked" + ] + }, + "naming": {} + }, + "entity-provider.tracking.JsonDocument": { + "name": "entity-provider.tracking.JsonDocument", + "body": { + "kind": "newtype", + "of": { + "kind": "primitive", + "name": "string" + }, + "invariants": [] + }, + "naming": {} + } + }, + "conversions": [], + "entities": {}, + "commands": { + "entity-provider.tracking.Batch": { + "name": "entity-provider.tracking.Batch", + "domain": "entity-provider.tracking", + "input": [ + { + "name": "request", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "response": [ + { + "name": "status", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "error", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "value", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "outcomes": [ + { + "name": "returned", + "condition": { + "kind": "otherwise" + }, + "test_strategy": "default_branch", + "emits": [], + "returns": true + } + ], + "naming": {} + }, + "entity-provider.tracking.Create": { + "name": "entity-provider.tracking.Create", + "domain": "entity-provider.tracking", + "input": [ + { + "name": "request", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "response": [ + { + "name": "status", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "error", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "value", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "outcomes": [ + { + "name": "returned", + "condition": { + "kind": "otherwise" + }, + "test_strategy": "default_branch", + "emits": [], + "returns": true + } + ], + "naming": {} + }, + "entity-provider.tracking.Histories": { + "name": "entity-provider.tracking.Histories", + "domain": "entity-provider.tracking", + "input": [ + { + "name": "request", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "response": [ + { + "name": "status", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "error", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "value", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "outcomes": [ + { + "name": "returned", + "condition": { + "kind": "otherwise" + }, + "test_strategy": "default_branch", + "emits": [], + "returns": true + } + ], + "naming": {} + }, + "entity-provider.tracking.Load": { + "name": "entity-provider.tracking.Load", + "domain": "entity-provider.tracking", + "input": [ + { + "name": "request", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "response": [ + { + "name": "status", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "error", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "value", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "outcomes": [ + { + "name": "returned", + "condition": { + "kind": "otherwise" + }, + "test_strategy": "default_branch", + "emits": [], + "returns": true + } + ], + "naming": {} + }, + "entity-provider.tracking.LookupBatch": { + "name": "entity-provider.tracking.LookupBatch", + "domain": "entity-provider.tracking", + "input": [ + { + "name": "request", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "response": [ + { + "name": "status", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "error", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "value", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "outcomes": [ + { + "name": "returned", + "condition": { + "kind": "otherwise" + }, + "test_strategy": "default_branch", + "emits": [], + "returns": true + } + ], + "naming": {} + }, + "entity-provider.tracking.Provision": { + "name": "entity-provider.tracking.Provision", + "domain": "entity-provider.tracking", + "input": [ + { + "name": "request", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "response": [ + { + "name": "status", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "error", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "value", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "outcomes": [ + { + "name": "returned", + "condition": { + "kind": "otherwise" + }, + "test_strategy": "default_branch", + "emits": [], + "returns": true + } + ], + "naming": {} + }, + "entity-provider.tracking.Register": { + "name": "entity-provider.tracking.Register", + "domain": "entity-provider.tracking", + "input": [ + { + "name": "request", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "response": [ + { + "name": "status", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "error", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "value", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "outcomes": [ + { + "name": "returned", + "condition": { + "kind": "otherwise" + }, + "test_strategy": "default_branch", + "emits": [], + "returns": true + } + ], + "naming": {} + }, + "entity-provider.tracking.Reopen": { + "name": "entity-provider.tracking.Reopen", + "domain": "entity-provider.tracking", + "input": [ + { + "name": "request", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "response": [ + { + "name": "status", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "error", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "value", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "outcomes": [ + { + "name": "returned", + "condition": { + "kind": "otherwise" + }, + "test_strategy": "default_branch", + "emits": [], + "returns": true + } + ], + "naming": {} + }, + "entity-provider.tracking.Snapshot": { + "name": "entity-provider.tracking.Snapshot", + "domain": "entity-provider.tracking", + "input": [ + { + "name": "request", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "response": [ + { + "name": "status", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "error", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "value", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "outcomes": [ + { + "name": "returned", + "condition": { + "kind": "otherwise" + }, + "test_strategy": "default_branch", + "emits": [], + "returns": true + } + ], + "naming": {} + }, + "entity-provider.tracking.SqlMutate": { + "name": "entity-provider.tracking.SqlMutate", + "domain": "entity-provider.tracking", + "input": [ + { + "name": "request", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "response": [ + { + "name": "status", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "error", + "type_ref": { + "kind": "primitive", + "name": "string" + }, + "naming": {} + }, + { + "name": "value", + "type_ref": { + "kind": "declared", + "name": "entity-provider.tracking.JsonDocument" + }, + "naming": {} + } + ], + "outcomes": [ + { + "name": "returned", + "condition": { + "kind": "otherwise" + }, + "test_strategy": "default_branch", + "emits": [], + "returns": true + } + ], + "naming": {} + } + }, + "events": {}, + "errors": {}, + "views": {}, + "actors": {}, + "bindings": {}, + "components": {}, + "workloads": {} +} diff --git a/ess/provider-tracking/generated/suite.json b/ess/provider-tracking/generated/suite.json new file mode 100644 index 00000000..00803dcb --- /dev/null +++ b/ess/provider-tracking/generated/suite.json @@ -0,0 +1,3976 @@ +{ + "coverage": { + "authored": [ + "entity-provider.tracking/authored/multi-subject-histories-one-scope", + "entity-provider.tracking/authored/unchanged-capture-retains-verified-state", + "entity-provider.tracking/authored/unchanged-head-blob-tamper", + "entity-provider.tracking/authored/unchanged-head-delete-blob-tamper", + "entity-provider.tracking/authored/unchanged-head-event-tamper", + "entity-provider.tracking/authored/unchanged-head-identity-tamper", + "entity-provider.tracking/authored/unchanged-head-projection-tamper" + ], + "authored_sources": { + "scenarios/multi-subject-histories-one-scope.yaml": { + "digest": "sha256:4dbfdea3e31a94baf8773e0880f5259c2c9df3ecb5dd734e40201fdd95177200", + "disposition": "accepted", + "scenario": "entity-provider.tracking/authored/multi-subject-histories-one-scope" + }, + "scenarios/unchanged-capture-retains-verified-state.yaml": { + "digest": "sha256:d84557f115a114ad3fe3d4aa7c9dc6d9de94f2a7d86bf61c317b2c698a2dddbe", + "disposition": "accepted", + "scenario": "entity-provider.tracking/authored/unchanged-capture-retains-verified-state" + }, + "scenarios/unchanged-head-blob-tamper.yaml": { + "digest": "sha256:a49c0980b9810135e756e9112c582d9937f5bd3522823af53f7af9af7454e7fc", + "disposition": "accepted", + "scenario": "entity-provider.tracking/authored/unchanged-head-blob-tamper" + }, + "scenarios/unchanged-head-delete-blob-tamper.yaml": { + "digest": "sha256:7c3e50c962926e48882c1e8b05da660eb2f6c1eb1c2936d941011e66c33f9c5f", + "disposition": "accepted", + "scenario": "entity-provider.tracking/authored/unchanged-head-delete-blob-tamper" + }, + "scenarios/unchanged-head-event-tamper.yaml": { + "digest": "sha256:c4806eabc95a2eda23ed452c93ac2e8bd0153e2c6cd370e06932204831b88157", + "disposition": "accepted", + "scenario": "entity-provider.tracking/authored/unchanged-head-event-tamper" + }, + "scenarios/unchanged-head-identity-tamper.yaml": { + "digest": "sha256:081646374902439d2b45b7f84ddfbeedb04ed5e782dbe4f660dd41f9afa23127", + "disposition": "accepted", + "scenario": "entity-provider.tracking/authored/unchanged-head-identity-tamper" + }, + "scenarios/unchanged-head-projection-tamper.yaml": { + "digest": "sha256:7c42f2cf77cdb639f092861e4037c1baee291006d8c546dfdaed78a373dc4e12", + "disposition": "accepted", + "scenario": "entity-provider.tracking/authored/unchanged-head-projection-tamper" + } + }, + "counts": { + "authored": 7, + "generated": 10, + "outside": 0, + "refused": 0 + }, + "generated": [ + "entity-provider.tracking.Batch/outcome/returned", + "entity-provider.tracking.Create/outcome/returned", + "entity-provider.tracking.Histories/outcome/returned", + "entity-provider.tracking.Load/outcome/returned", + "entity-provider.tracking.LookupBatch/outcome/returned", + "entity-provider.tracking.Provision/outcome/returned", + "entity-provider.tracking.Register/outcome/returned", + "entity-provider.tracking.Reopen/outcome/returned", + "entity-provider.tracking.Snapshot/outcome/returned", + "entity-provider.tracking.SqlMutate/outcome/returned" + ], + "knowledge": "complete_inventory", + "outside": [], + "refused": [], + "selection": { + "filter": { + "kind": "all" + }, + "origins": "generated_and_authored", + "scope": { + "kind": "system" + } + } + }, + "provenance": { + "contract_digest": "beb32d6f1713c8112707fde7a29fcd08085bc2e510383007639eab6e1a3dbe80", + "spec_digest": "ef6eb41f4dac9a7d51a5c0dc675933062d24096f195456a1dafe88ff8de672ac", + "specification_version": "v1", + "suite_version": "ess-conformance/29", + "system": "entity-provider" + }, + "scenarios": { + "entity-provider.tracking.Batch/outcome/returned": { + "purpose": "`entity-provider.tracking.Batch` answers `returned` for an input no other branch's guard claims", + "source": [ + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Batch" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Batch", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Batch", + "input": { + "request": { + "kind": "literal", + "value": "request" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Batch", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Batch", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": {}, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Batch", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking.Create/outcome/returned": { + "purpose": "`entity-provider.tracking.Create` answers `returned` for an input no other branch's guard claims", + "source": [ + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Create" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Create", + "input": { + "request": { + "kind": "literal", + "value": "request" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Create", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": {}, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking.Histories/outcome/returned": { + "purpose": "`entity-provider.tracking.Histories` answers `returned` for an input no other branch's guard claims", + "source": [ + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Histories" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Histories", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Histories", + "input": { + "request": { + "kind": "literal", + "value": "request" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Histories", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Histories", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": {}, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Histories", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking.Load/outcome/returned": { + "purpose": "`entity-provider.tracking.Load` answers `returned` for an input no other branch's guard claims", + "source": [ + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Load" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Load", + "input": { + "request": { + "kind": "literal", + "value": "request" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Load", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": {}, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking.LookupBatch/outcome/returned": { + "purpose": "`entity-provider.tracking.LookupBatch` answers `returned` for an input no other branch's guard claims", + "source": [ + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.LookupBatch" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.LookupBatch", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.LookupBatch", + "input": { + "request": { + "kind": "literal", + "value": "request" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.LookupBatch", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.LookupBatch", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": {}, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.LookupBatch", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking.Provision/outcome/returned": { + "purpose": "`entity-provider.tracking.Provision` answers `returned` for an input no other branch's guard claims", + "source": [ + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Provision" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Provision", + "input": { + "request": { + "kind": "literal", + "value": "request" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Provision", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": {}, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking.Register/outcome/returned": { + "purpose": "`entity-provider.tracking.Register` answers `returned` for an input no other branch's guard claims", + "source": [ + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Register" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Register", + "input": { + "request": { + "kind": "literal", + "value": "request" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Register", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": {}, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking.Reopen/outcome/returned": { + "purpose": "`entity-provider.tracking.Reopen` answers `returned` for an input no other branch's guard claims", + "source": [ + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Reopen" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Reopen", + "input": { + "request": { + "kind": "literal", + "value": "request" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Reopen", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": {}, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking.Snapshot/outcome/returned": { + "purpose": "`entity-provider.tracking.Snapshot` answers `returned` for an input no other branch's guard claims", + "source": [ + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Snapshot" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Snapshot", + "input": { + "request": { + "kind": "literal", + "value": "request" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Snapshot", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": {}, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking.SqlMutate/outcome/returned": { + "purpose": "`entity-provider.tracking.SqlMutate` answers `returned` for an input no other branch's guard claims", + "source": [ + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.SqlMutate" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.SqlMutate", + "input": { + "request": { + "kind": "literal", + "value": "request" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.SqlMutate", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": {}, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking/authored/multi-subject-histories-one-scope": { + "purpose": "A scoped facade read preserves requested order, duplicate and missing subjects, exact record identities and revisions, and transitioned state after an atomic multi-subject batch.", + "source": [ + { + "kind": "domain", + "name": "entity-provider.tracking" + }, + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Batch" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Create" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Histories" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Load" + }, + { + "kind": "command", + "name": "entity-provider.tracking.LookupBatch" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Provision" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Register" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Reopen" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Snapshot" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Batch", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Histories", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.LookupBatch", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"entity\":\"ticket\",\"version\":1,\"schema\":{\"fields\":{}},\"lifecycle\":{\"initial\":\"Open\",\"states\":[\"Open\",\"Closed\"]},\"operations\":{\"Close\":{\"transitions\":[{\"from\":\"Open\",\"to\":\"Closed\"}],\"emits\":[]}}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Register", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Provision", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Provision", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Create", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\",\"version\":1,\"fields\":{},\"recording\":{\"record_id\":\"create-a\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Create", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Batch", + "input": { + "request": { + "kind": "literal", + "value": "{\"key\":{\"named\":\"pair\"},\"actions\":[{\"kind\":\"execute\",\"entity\":\"ticket\",\"id\":\"a\",\"expected_revision\":1,\"operation\":\"Close\",\"arguments\":{},\"recording\":{\"record_id\":\"close-a\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}},{\"kind\":\"create\",\"entity\":\"ticket\",\"id\":\"b\",\"version\":1,\"fields\":{},\"recording\":{\"record_id\":\"create-b\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}]}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Batch", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Batch", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Batch", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Histories", + "input": { + "request": { + "kind": "literal", + "value": "{\"subjects\":[{\"entity\":\"ticket\",\"id\":\"b\"},{\"entity\":\"ticket\",\"id\":\"a\"},{\"entity\":\"ticket\",\"id\":\"b\"},{\"entity\":\"ticket\",\"id\":\"missing\"}]}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Histories", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Histories", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "[{\"entity\":\"ticket\",\"id\":\"b\",\"records\":[{\"record_id\":\"create-b\",\"revision\":1}]},{\"entity\":\"ticket\",\"id\":\"a\",\"records\":[{\"record_id\":\"create-a\",\"revision\":1},{\"record_id\":\"close-a\",\"revision\":2}]},{\"entity\":\"ticket\",\"id\":\"b\",\"records\":[{\"record_id\":\"create-b\",\"revision\":1}]},{\"entity\":\"ticket\",\"id\":\"missing\",\"records\":[]}]" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Histories", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Load", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Load", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"entity\":\"ticket\",\"fields\":{},\"id\":\"a\",\"lifecycle_state\":\"Closed\",\"revision\":2,\"version\":1}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.LookupBatch", + "input": { + "request": { + "kind": "literal", + "value": "{\"key\":{\"named\":\"pair\"}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.LookupBatch", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.LookupBatch", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"present\":true}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.LookupBatch", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Histories", + "input": { + "request": { + "kind": "literal", + "value": "{\"subjects\":[]}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Histories", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Histories", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "[]" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Histories", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Reopen", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"FullVerification\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Reopen", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Snapshot", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Snapshot", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "[{\"entity\":\"ticket\",\"id\":\"a\",\"records\":[{\"record_id\":\"create-a\",\"revision\":1},{\"record_id\":\"close-a\",\"revision\":2}]},{\"entity\":\"ticket\",\"id\":\"b\",\"records\":[{\"record_id\":\"create-b\",\"revision\":1}]}]" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Load", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Load", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"entity\":\"ticket\",\"fields\":{},\"id\":\"a\",\"lifecycle_state\":\"Closed\",\"revision\":2,\"version\":1}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking/authored/unchanged-capture-retains-verified-state": { + "purpose": "Repeated complete reads preserve exact record identities and revisions, and transitioned state matches a full-verification reopen.", + "source": [ + { + "kind": "domain", + "name": "entity-provider.tracking" + }, + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Batch" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Create" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Load" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Provision" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Register" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Reopen" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Snapshot" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Batch", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"entity\":\"ticket\",\"version\":1,\"schema\":{\"fields\":{}},\"lifecycle\":{\"initial\":\"Open\",\"states\":[\"Open\",\"Closed\"]},\"operations\":{\"Close\":{\"transitions\":[{\"from\":\"Open\",\"to\":\"Closed\"}],\"emits\":[]}}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Register", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Provision", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Provision", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Create", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\",\"version\":1,\"fields\":{},\"recording\":{\"record_id\":\"create-a\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Create", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Batch", + "input": { + "request": { + "kind": "literal", + "value": "{\"key\":{\"named\":\"close\"},\"actions\":[{\"kind\":\"execute\",\"entity\":\"ticket\",\"id\":\"a\",\"expected_revision\":1,\"operation\":\"Close\",\"arguments\":{},\"recording\":{\"record_id\":\"close-a\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}]}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Batch", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Batch", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Batch", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Load", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Load", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"entity\":\"ticket\",\"fields\":{},\"id\":\"a\",\"lifecycle_state\":\"Closed\",\"revision\":2,\"version\":1}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Snapshot", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Snapshot", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "[{\"entity\":\"ticket\",\"id\":\"a\",\"records\":[{\"record_id\":\"create-a\",\"revision\":1},{\"record_id\":\"close-a\",\"revision\":2}]}]" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Snapshot", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Snapshot", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "[{\"entity\":\"ticket\",\"id\":\"a\",\"records\":[{\"record_id\":\"create-a\",\"revision\":1},{\"record_id\":\"close-a\",\"revision\":2}]}]" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Reopen", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"FullVerification\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Reopen", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Snapshot", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Snapshot", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "[{\"entity\":\"ticket\",\"id\":\"a\",\"records\":[{\"record_id\":\"create-a\",\"revision\":1},{\"record_id\":\"close-a\",\"revision\":2}]}]" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Load", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Load", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"entity\":\"ticket\",\"fields\":{},\"id\":\"a\",\"lifecycle_state\":\"Closed\",\"revision\":2,\"version\":1}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Load", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking/authored/unchanged-head-blob-tamper": { + "purpose": "A second SQLite connection changes blob content without advancing the event head; a warm tracked read and reopen refuse.", + "source": [ + { + "kind": "domain", + "name": "entity-provider.tracking" + }, + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Create" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Provision" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Register" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Reopen" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Snapshot" + }, + { + "kind": "command", + "name": "entity-provider.tracking.SqlMutate" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"entity\":\"ticket\",\"version\":1,\"schema\":{\"fields\":{}},\"lifecycle\":{\"initial\":\"Open\",\"states\":[\"Open\"]},\"operations\":{\"Touch\":{\"transitions\":[{\"from\":\"Open\",\"to\":\"Open\"}],\"emits\":[]}}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Register", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Provision", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Provision", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Create", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\",\"version\":1,\"fields\":{},\"recording\":{\"record_id\":\"create-a\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Create", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.SqlMutate", + "input": { + "request": { + "kind": "literal", + "value": "{\"kind\":\"blob\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.SqlMutate", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"changed\":1,\"same_event_head\":true}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Snapshot", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Snapshot", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "ProviderIntegrity", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Reopen", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Reopen", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "ProviderIntegrity", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking/authored/unchanged-head-delete-blob-tamper": { + "purpose": "A second SQLite connection changes delete-blob content without advancing the event head; a warm tracked read and reopen refuse.", + "source": [ + { + "kind": "domain", + "name": "entity-provider.tracking" + }, + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Create" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Provision" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Register" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Reopen" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Snapshot" + }, + { + "kind": "command", + "name": "entity-provider.tracking.SqlMutate" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"entity\":\"ticket\",\"version\":1,\"schema\":{\"fields\":{}},\"lifecycle\":{\"initial\":\"Open\",\"states\":[\"Open\"]},\"operations\":{\"Touch\":{\"transitions\":[{\"from\":\"Open\",\"to\":\"Open\"}],\"emits\":[]}}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Register", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Provision", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Provision", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Create", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\",\"version\":1,\"fields\":{},\"recording\":{\"record_id\":\"create-a\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Create", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.SqlMutate", + "input": { + "request": { + "kind": "literal", + "value": "{\"kind\":\"delete-blob\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.SqlMutate", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"changed\":1,\"same_event_head\":true}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Snapshot", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Snapshot", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "ProviderIntegrity", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Reopen", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Reopen", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "ProviderIntegrity", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking/authored/unchanged-head-event-tamper": { + "purpose": "A second SQLite connection changes event content without advancing the event head; a warm tracked read and reopen refuse.", + "source": [ + { + "kind": "domain", + "name": "entity-provider.tracking" + }, + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Create" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Provision" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Register" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Reopen" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Snapshot" + }, + { + "kind": "command", + "name": "entity-provider.tracking.SqlMutate" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"entity\":\"ticket\",\"version\":1,\"schema\":{\"fields\":{}},\"lifecycle\":{\"initial\":\"Open\",\"states\":[\"Open\"]},\"operations\":{\"Touch\":{\"transitions\":[{\"from\":\"Open\",\"to\":\"Open\"}],\"emits\":[]}}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Register", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Provision", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Provision", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Create", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\",\"version\":1,\"fields\":{},\"recording\":{\"record_id\":\"create-a\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Create", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.SqlMutate", + "input": { + "request": { + "kind": "literal", + "value": "{\"kind\":\"event\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.SqlMutate", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"changed\":1,\"same_event_head\":true}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Snapshot", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Snapshot", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "ProviderIntegrity", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Reopen", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Reopen", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "ProviderIntegrity", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking/authored/unchanged-head-identity-tamper": { + "purpose": "A second SQLite connection changes identity content without advancing the event head; a warm tracked read and reopen refuse.", + "source": [ + { + "kind": "domain", + "name": "entity-provider.tracking" + }, + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Create" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Provision" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Register" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Reopen" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Snapshot" + }, + { + "kind": "command", + "name": "entity-provider.tracking.SqlMutate" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"entity\":\"ticket\",\"version\":1,\"schema\":{\"fields\":{}},\"lifecycle\":{\"initial\":\"Open\",\"states\":[\"Open\"]},\"operations\":{\"Touch\":{\"transitions\":[{\"from\":\"Open\",\"to\":\"Open\"}],\"emits\":[]}}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Register", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Provision", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Provision", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Create", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\",\"version\":1,\"fields\":{},\"recording\":{\"record_id\":\"create-a\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Create", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.SqlMutate", + "input": { + "request": { + "kind": "literal", + "value": "{\"kind\":\"identity\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.SqlMutate", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"changed\":1,\"same_event_head\":true}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Snapshot", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Snapshot", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "ProviderIntegrity", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Reopen", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Reopen", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "ProviderIntegrity", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + }, + "entity-provider.tracking/authored/unchanged-head-projection-tamper": { + "purpose": "A second SQLite connection changes projection content without advancing the event head; a warm tracked read and reopen refuse.", + "source": [ + { + "kind": "domain", + "name": "entity-provider.tracking" + }, + { + "kind": "type", + "name": "entity-provider.tracking.JsonDocument" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Create" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Provision" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Register" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Reopen" + }, + { + "kind": "command", + "name": "entity-provider.tracking.Snapshot" + }, + { + "kind": "command", + "name": "entity-provider.tracking.SqlMutate" + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + { + "kind": "outcome", + "name": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + } + ], + "steps": [ + { + "command": "entity-provider.tracking.Register", + "input": { + "request": { + "kind": "literal", + "value": "{\"definition\":{\"entity\":\"ticket\",\"version\":1,\"schema\":{\"fields\":{}},\"lifecycle\":{\"initial\":\"Open\",\"states\":[\"Open\"]},\"operations\":{\"Touch\":{\"transitions\":[{\"from\":\"Open\",\"to\":\"Open\"}],\"emits\":[]}}}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Register", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Register", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Provision", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Provision", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Provision", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Create", + "input": { + "request": { + "kind": "literal", + "value": "{\"entity\":\"ticket\",\"id\":\"a\",\"version\":1,\"fields\":{},\"recording\":{\"record_id\":\"create-a\",\"recorded_at\":\"2026-10-03T00:00:00Z\",\"actor\":null,\"correlation\":null,\"causation\":null}}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Create", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Create", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.SqlMutate", + "input": { + "request": { + "kind": "literal", + "value": "{\"kind\":\"projection\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.SqlMutate", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "status": "ok", + "value": "{\"changed\":1,\"same_event_head\":true}" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.SqlMutate", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Snapshot", + "input": { + "request": { + "kind": "literal", + "value": "{}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Snapshot", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "ProviderIntegrity", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Snapshot", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + }, + { + "command": "entity-provider.tracking.Reopen", + "input": { + "request": { + "kind": "literal", + "value": "{\"policy\":\"ProviderTracked\"}" + } + }, + "step": "execute_command" + }, + { + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + }, + "step": "expect_outcome" + }, + { + "response": { + "command": "entity-provider.tracking.Reopen", + "declarations": { + "entity-provider.tracking.JsonDocument": { + "kind": "newtype", + "of": "String" + } + }, + "expected": { + "error": "ProviderIntegrity", + "status": "error" + }, + "fields": [ + { + "name": "status", + "type": "String" + }, + { + "name": "error", + "type": "String" + }, + { + "name": "value", + "type": "entity-provider.tracking.JsonDocument" + } + ], + "outcome": { + "command": "entity-provider.tracking.Reopen", + "outcome": "returned" + } + }, + "step": "expect_direct_response" + } + ] + } + } +} diff --git a/ess/provider-tracking/scenarios/multi-subject-histories-one-scope.yaml b/ess/provider-tracking/scenarios/multi-subject-histories-one-scope.yaml new file mode 100644 index 00000000..b83ad07d --- /dev/null +++ b/ess/provider-tracking/scenarios/multi-subject-histories-one-scope.yaml @@ -0,0 +1,69 @@ +type: ess-scenario/4 +domain: entity-provider.tracking +scenario: multi-subject-histories-one-scope +summary: A scoped facade read preserves requested order, duplicate and missing subjects, exact record identities and revisions, and transitioned state after an atomic multi-subject batch. +# This is semantic/integrity evidence; the one-scope call bound is proved separately by runtime counters. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity-provider.tracking.Register + input: + request: '{"definition":{"entity":"ticket","version":1,"schema":{"fields":{}},"lifecycle":{"initial":"Open","states":["Open","Closed"]},"operations":{"Close":{"transitions":[{"from":"Open","to":"Closed"}],"emits":[]}}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity-provider.tracking.Provision + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:02Z + command: entity-provider.tracking.Create + input: + request: '{"entity":"ticket","id":"a","version":1,"fields":{},"recording":{"record_id":"create-a","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:03Z + command: entity-provider.tracking.Batch + input: + request: '{"key":{"named":"pair"},"actions":[{"kind":"execute","entity":"ticket","id":"a","expected_revision":1,"operation":"Close","arguments":{},"recording":{"record_id":"close-a","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}},{"kind":"create","entity":"ticket","id":"b","version":1,"fields":{},"recording":{"record_id":"create-b","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}]}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:04Z + command: entity-provider.tracking.Histories + input: + request: '{"subjects":[{"entity":"ticket","id":"b"},{"entity":"ticket","id":"a"},{"entity":"ticket","id":"b"},{"entity":"ticket","id":"missing"}]}' + outcome: returned + response: + status: ok + value: '[{"entity":"ticket","id":"b","records":[{"record_id":"create-b","revision":1}]},{"entity":"ticket","id":"a","records":[{"record_id":"create-a","revision":1},{"record_id":"close-a","revision":2}]},{"entity":"ticket","id":"b","records":[{"record_id":"create-b","revision":1}]},{"entity":"ticket","id":"missing","records":[]}]' + - at: 2026-10-03T00:00:05Z + command: entity-provider.tracking.Load + input: {request: '{"entity":"ticket","id":"a"}'} + outcome: returned + response: {status: ok, value: '{"entity":"ticket","fields":{},"id":"a","lifecycle_state":"Closed","revision":2,"version":1}'} + - at: 2026-10-03T00:00:06Z + command: entity-provider.tracking.LookupBatch + input: {request: '{"key":{"named":"pair"}}'} + outcome: returned + response: {status: ok, value: '{"present":true}'} + - at: 2026-10-03T00:00:07Z + command: entity-provider.tracking.Histories + input: {request: '{"subjects":[]}'} + outcome: returned + response: {status: ok, value: '[]'} + - at: 2026-10-03T00:00:08Z + command: entity-provider.tracking.Reopen + input: {request: '{"policy":"FullVerification"}'} + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:09Z + command: entity-provider.tracking.Snapshot + input: {request: '{}'} + outcome: returned + response: + status: ok + value: '[{"entity":"ticket","id":"a","records":[{"record_id":"create-a","revision":1},{"record_id":"close-a","revision":2}]},{"entity":"ticket","id":"b","records":[{"record_id":"create-b","revision":1}]}]' + - at: 2026-10-03T00:00:10Z + command: entity-provider.tracking.Load + input: {request: '{"entity":"ticket","id":"a"}'} + outcome: returned + response: {status: ok, value: '{"entity":"ticket","fields":{},"id":"a","lifecycle_state":"Closed","revision":2,"version":1}'} diff --git a/ess/provider-tracking/scenarios/unchanged-capture-retains-verified-state.yaml b/ess/provider-tracking/scenarios/unchanged-capture-retains-verified-state.yaml new file mode 100644 index 00000000..c3b840b6 --- /dev/null +++ b/ess/provider-tracking/scenarios/unchanged-capture-retains-verified-state.yaml @@ -0,0 +1,59 @@ +type: ess-scenario/4 +domain: entity-provider.tracking +scenario: unchanged-capture-retains-verified-state +summary: Repeated complete reads preserve exact record identities and revisions, and transitioned state matches a full-verification reopen. +# This is semantic/integrity evidence; retained-path cost is measured by runtime counters and performance tests. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity-provider.tracking.Register + input: + request: '{"definition":{"entity":"ticket","version":1,"schema":{"fields":{}},"lifecycle":{"initial":"Open","states":["Open","Closed"]},"operations":{"Close":{"transitions":[{"from":"Open","to":"Closed"}],"emits":[]}}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity-provider.tracking.Provision + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:02Z + command: entity-provider.tracking.Create + input: + request: '{"entity":"ticket","id":"a","version":1,"fields":{},"recording":{"record_id":"create-a","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:03Z + command: entity-provider.tracking.Batch + input: + request: '{"key":{"named":"close"},"actions":[{"kind":"execute","entity":"ticket","id":"a","expected_revision":1,"operation":"Close","arguments":{},"recording":{"record_id":"close-a","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}]}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:04Z + command: entity-provider.tracking.Load + input: {request: '{"entity":"ticket","id":"a"}'} + outcome: returned + response: {status: ok, value: '{"entity":"ticket","fields":{},"id":"a","lifecycle_state":"Closed","revision":2,"version":1}'} + - at: 2026-10-03T00:00:05Z + command: entity-provider.tracking.Snapshot + input: {request: '{}'} + outcome: returned + response: {status: ok, value: '[{"entity":"ticket","id":"a","records":[{"record_id":"create-a","revision":1},{"record_id":"close-a","revision":2}]}]'} + - at: 2026-10-03T00:00:06Z + command: entity-provider.tracking.Snapshot + input: {request: '{}'} + outcome: returned + response: {status: ok, value: '[{"entity":"ticket","id":"a","records":[{"record_id":"create-a","revision":1},{"record_id":"close-a","revision":2}]}]'} + - at: 2026-10-03T00:00:07Z + command: entity-provider.tracking.Reopen + input: {request: '{"policy":"FullVerification"}'} + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:08Z + command: entity-provider.tracking.Snapshot + input: {request: '{}'} + outcome: returned + response: {status: ok, value: '[{"entity":"ticket","id":"a","records":[{"record_id":"create-a","revision":1},{"record_id":"close-a","revision":2}]}]'} + - at: 2026-10-03T00:00:09Z + command: entity-provider.tracking.Load + input: {request: '{"entity":"ticket","id":"a"}'} + outcome: returned + response: {status: ok, value: '{"entity":"ticket","fields":{},"id":"a","lifecycle_state":"Closed","revision":2,"version":1}'} diff --git a/ess/provider-tracking/scenarios/unchanged-head-blob-tamper.yaml b/ess/provider-tracking/scenarios/unchanged-head-blob-tamper.yaml new file mode 100644 index 00000000..fafad656 --- /dev/null +++ b/ess/provider-tracking/scenarios/unchanged-head-blob-tamper.yaml @@ -0,0 +1,37 @@ +type: ess-scenario/4 +domain: entity-provider.tracking +scenario: unchanged-head-blob-tamper +summary: A second SQLite connection changes blob content without advancing the event head; a warm tracked read and reopen refuse. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity-provider.tracking.Register + input: + request: '{"definition":{"entity":"ticket","version":1,"schema":{"fields":{}},"lifecycle":{"initial":"Open","states":["Open"]},"operations":{"Touch":{"transitions":[{"from":"Open","to":"Open"}],"emits":[]}}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity-provider.tracking.Provision + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:02Z + command: entity-provider.tracking.Create + input: + request: '{"entity":"ticket","id":"a","version":1,"fields":{},"recording":{"record_id":"create-a","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:03Z + command: entity-provider.tracking.SqlMutate + input: {request: '{"kind":"blob"}'} + outcome: returned + response: {status: ok, value: '{"changed":1,"same_event_head":true}'} + - at: 2026-10-03T00:00:04Z + command: entity-provider.tracking.Snapshot + input: {request: '{}'} + outcome: returned + response: {status: error, error: ProviderIntegrity} + - at: 2026-10-03T00:00:05Z + command: entity-provider.tracking.Reopen + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: error, error: ProviderIntegrity} diff --git a/ess/provider-tracking/scenarios/unchanged-head-delete-blob-tamper.yaml b/ess/provider-tracking/scenarios/unchanged-head-delete-blob-tamper.yaml new file mode 100644 index 00000000..432bef70 --- /dev/null +++ b/ess/provider-tracking/scenarios/unchanged-head-delete-blob-tamper.yaml @@ -0,0 +1,37 @@ +type: ess-scenario/4 +domain: entity-provider.tracking +scenario: unchanged-head-delete-blob-tamper +summary: A second SQLite connection changes delete-blob content without advancing the event head; a warm tracked read and reopen refuse. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity-provider.tracking.Register + input: + request: '{"definition":{"entity":"ticket","version":1,"schema":{"fields":{}},"lifecycle":{"initial":"Open","states":["Open"]},"operations":{"Touch":{"transitions":[{"from":"Open","to":"Open"}],"emits":[]}}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity-provider.tracking.Provision + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:02Z + command: entity-provider.tracking.Create + input: + request: '{"entity":"ticket","id":"a","version":1,"fields":{},"recording":{"record_id":"create-a","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:03Z + command: entity-provider.tracking.SqlMutate + input: {request: '{"kind":"delete-blob"}'} + outcome: returned + response: {status: ok, value: '{"changed":1,"same_event_head":true}'} + - at: 2026-10-03T00:00:04Z + command: entity-provider.tracking.Snapshot + input: {request: '{}'} + outcome: returned + response: {status: error, error: ProviderIntegrity} + - at: 2026-10-03T00:00:05Z + command: entity-provider.tracking.Reopen + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: error, error: ProviderIntegrity} diff --git a/ess/provider-tracking/scenarios/unchanged-head-event-tamper.yaml b/ess/provider-tracking/scenarios/unchanged-head-event-tamper.yaml new file mode 100644 index 00000000..71ff0dc2 --- /dev/null +++ b/ess/provider-tracking/scenarios/unchanged-head-event-tamper.yaml @@ -0,0 +1,37 @@ +type: ess-scenario/4 +domain: entity-provider.tracking +scenario: unchanged-head-event-tamper +summary: A second SQLite connection changes event content without advancing the event head; a warm tracked read and reopen refuse. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity-provider.tracking.Register + input: + request: '{"definition":{"entity":"ticket","version":1,"schema":{"fields":{}},"lifecycle":{"initial":"Open","states":["Open"]},"operations":{"Touch":{"transitions":[{"from":"Open","to":"Open"}],"emits":[]}}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity-provider.tracking.Provision + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:02Z + command: entity-provider.tracking.Create + input: + request: '{"entity":"ticket","id":"a","version":1,"fields":{},"recording":{"record_id":"create-a","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:03Z + command: entity-provider.tracking.SqlMutate + input: {request: '{"kind":"event"}'} + outcome: returned + response: {status: ok, value: '{"changed":1,"same_event_head":true}'} + - at: 2026-10-03T00:00:04Z + command: entity-provider.tracking.Snapshot + input: {request: '{}'} + outcome: returned + response: {status: error, error: ProviderIntegrity} + - at: 2026-10-03T00:00:05Z + command: entity-provider.tracking.Reopen + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: error, error: ProviderIntegrity} diff --git a/ess/provider-tracking/scenarios/unchanged-head-identity-tamper.yaml b/ess/provider-tracking/scenarios/unchanged-head-identity-tamper.yaml new file mode 100644 index 00000000..dc50b5dd --- /dev/null +++ b/ess/provider-tracking/scenarios/unchanged-head-identity-tamper.yaml @@ -0,0 +1,37 @@ +type: ess-scenario/4 +domain: entity-provider.tracking +scenario: unchanged-head-identity-tamper +summary: A second SQLite connection changes identity content without advancing the event head; a warm tracked read and reopen refuse. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity-provider.tracking.Register + input: + request: '{"definition":{"entity":"ticket","version":1,"schema":{"fields":{}},"lifecycle":{"initial":"Open","states":["Open"]},"operations":{"Touch":{"transitions":[{"from":"Open","to":"Open"}],"emits":[]}}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity-provider.tracking.Provision + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:02Z + command: entity-provider.tracking.Create + input: + request: '{"entity":"ticket","id":"a","version":1,"fields":{},"recording":{"record_id":"create-a","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:03Z + command: entity-provider.tracking.SqlMutate + input: {request: '{"kind":"identity"}'} + outcome: returned + response: {status: ok, value: '{"changed":1,"same_event_head":true}'} + - at: 2026-10-03T00:00:04Z + command: entity-provider.tracking.Snapshot + input: {request: '{}'} + outcome: returned + response: {status: error, error: ProviderIntegrity} + - at: 2026-10-03T00:00:05Z + command: entity-provider.tracking.Reopen + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: error, error: ProviderIntegrity} diff --git a/ess/provider-tracking/scenarios/unchanged-head-projection-tamper.yaml b/ess/provider-tracking/scenarios/unchanged-head-projection-tamper.yaml new file mode 100644 index 00000000..16a26cb4 --- /dev/null +++ b/ess/provider-tracking/scenarios/unchanged-head-projection-tamper.yaml @@ -0,0 +1,37 @@ +type: ess-scenario/4 +domain: entity-provider.tracking +scenario: unchanged-head-projection-tamper +summary: A second SQLite connection changes projection content without advancing the event head; a warm tracked read and reopen refuse. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity-provider.tracking.Register + input: + request: '{"definition":{"entity":"ticket","version":1,"schema":{"fields":{}},"lifecycle":{"initial":"Open","states":["Open"]},"operations":{"Touch":{"transitions":[{"from":"Open","to":"Open"}],"emits":[]}}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity-provider.tracking.Provision + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:02Z + command: entity-provider.tracking.Create + input: + request: '{"entity":"ticket","id":"a","version":1,"fields":{},"recording":{"record_id":"create-a","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:03Z + command: entity-provider.tracking.SqlMutate + input: {request: '{"kind":"projection"}'} + outcome: returned + response: {status: ok, value: '{"changed":1,"same_event_head":true}'} + - at: 2026-10-03T00:00:04Z + command: entity-provider.tracking.Snapshot + input: {request: '{}'} + outcome: returned + response: {status: error, error: ProviderIntegrity} + - at: 2026-10-03T00:00:05Z + command: entity-provider.tracking.Reopen + input: {request: '{"policy":"ProviderTracked"}'} + outcome: returned + response: {status: error, error: ProviderIntegrity} diff --git a/ess/provider-tracking/system.yaml b/ess/provider-tracking/system.yaml new file mode 100644 index 00000000..45b09920 --- /dev/null +++ b/ess/provider-tracking/system.yaml @@ -0,0 +1,5 @@ +format: ess/17 +system: entity-provider +version: v1 +domains: + - entity-provider.tracking diff --git a/ess/scenarios/executor/issue-50-batch-refusal-is-atomic.yaml b/ess/scenarios/executor/issue-50-batch-refusal-is-atomic.yaml new file mode 100644 index 00000000..e8b31a0f --- /dev/null +++ b/ess/scenarios/executor/issue-50-batch-refusal-is-atomic.yaml @@ -0,0 +1,36 @@ +type: ess-scenario/4 +domain: entity.executor +scenario: issue-50-batch-refusal-is-atomic +summary: A version-bound input refusal leaves an earlier transaction-local creation uncommitted. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity.executor.Register + input: + request: '{"definition":{"entity":"invoice","version":1,"semantics":"service/3","schema":{"fields":{}},"lifecycle":{"initial":"Open","states":["Open"]},"operations":{"Pay":{"arguments":{"fields":{"accept":{"type":"boolean","required":true}}},"outcomes":[{"name":"rejected","when":{"eq":["$args.accept",false]},"refuses":{"error":"InputRejected","message":"Input was refused"}},{"name":"paid","effect":"updates"}]}}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity.executor.Batch + input: + request: '{"key":{"named":"atomic"},"actions":[{"kind":"create","entity":"invoice","id":"a","version":1,"fields":{},"recording":{"record_id":"created","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}},{"kind":"execute","entity":"invoice","id":"a","definition_version":1,"expected_revision":1,"operation":"Pay","arguments":{"accept":false},"recording":{"record_id":"refused","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}]}' + outcome: returned + response: + status: error + error: Refused + value: >- + {"detail":"outcome 'rejected' refuses with 'InputRejected': Input was refused"} + - at: 2026-10-03T00:00:02Z + command: entity.executor.Load + input: {request: '{"entity":"invoice","id":"a"}'} + outcome: returned + response: {status: ok, value: 'null'} + - at: 2026-10-03T00:00:03Z + command: entity.executor.LookupRecord + input: {request: '{"record_id":"created"}'} + outcome: returned + response: {status: ok, value: 'null'} + - at: 2026-10-03T00:00:04Z + command: entity.executor.LookupRecord + input: {request: '{"record_id":"refused"}'} + outcome: returned + response: {status: ok, value: 'null'} diff --git a/ess/scenarios/executor/issue-50-input-refusal-existing-create.yaml b/ess/scenarios/executor/issue-50-input-refusal-existing-create.yaml new file mode 100644 index 00000000..6d3bf717 --- /dev/null +++ b/ess/scenarios/executor/issue-50-input-refusal-existing-create.yaml @@ -0,0 +1,47 @@ +type: ess-scenario/4 +domain: entity.executor +scenario: executor-input-refusal-existing-create +summary: A declared creation refusal precedes existence and leaves the original row and history intact. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity.executor.Register + input: + request: '{"definition":{"entity":"invoice","version":1,"semantics":"service/3","schema":{"fields":{}},"lifecycle":{"initial":"Open","states":["Open"]},"create":{"arguments":{"fields":{"accept":{"type":"boolean","required":true}}},"outcomes":[{"name":"rejected","when":{"eq":["$args.accept",false]},"refuses":{"error":"InputRejected","message":"Input was refused"}},{"name":"created","effect":"creates"}]}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity.executor.Create + input: + request: '{"entity":"invoice","id":"a","version":1,"fields":{"accept":true},"recording":{"record_id":"created","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:02Z + command: entity.executor.ClearTrace + input: {request: '{}'} + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:03Z + command: entity.executor.Create + input: + request: '{"entity":"invoice","id":"a","version":1,"fields":{"accept":false},"recording":{"record_id":"refused","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}' + outcome: returned + response: + status: error + error: Refused + value: >- + {"detail":"outcome 'rejected' refuses with 'InputRejected': Input was refused"} + - at: 2026-10-03T00:00:04Z + command: entity.executor.Trace + input: {request: '{}'} + outcome: returned + response: {status: ok, value: '["lookup_batch","lookup_record:refused"]'} + - at: 2026-10-03T00:00:05Z + command: entity.executor.Load + input: {request: '{"entity":"invoice","id":"a"}'} + outcome: returned + response: {status: ok, value: '{"entity":"invoice","fields":{},"id":"a","lifecycle_state":"Open","revision":1,"version":1}'} + - at: 2026-10-03T00:00:06Z + command: entity.executor.LookupRecord + input: {request: '{"record_id":"refused"}'} + outcome: returned + response: {status: ok, value: 'null'} diff --git a/ess/scenarios/executor/issue-50-input-refusal-missing-execute.yaml b/ess/scenarios/executor/issue-50-input-refusal-missing-execute.yaml new file mode 100644 index 00000000..b1510566 --- /dev/null +++ b/ess/scenarios/executor/issue-50-input-refusal-missing-execute.yaml @@ -0,0 +1,36 @@ +type: ess-scenario/4 +domain: entity.executor +scenario: executor-input-refusal-missing-execute +summary: An explicit definition selects the declared input refusal before a missing subject is loaded. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity.executor.Register + input: + request: '{"definition":{"entity":"invoice","version":1,"semantics":"service/3","schema":{"fields":{}},"lifecycle":{"initial":"Open","states":["Open"]},"operations":{"Pay":{"arguments":{"fields":{"accept":{"type":"boolean","required":true}}},"outcomes":[{"name":"rejected","when":{"eq":["$args.accept",false]},"refuses":{"error":"InputRejected","message":"Input was refused"}},{"name":"paid","effect":"updates"}]}}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity.executor.Execute + input: + request: '{"entity":"invoice","id":"missing","definition_version":1,"expected_revision":1,"operation":"Pay","arguments":{"accept":false},"recording":{"record_id":"refused","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}' + outcome: returned + response: + status: error + error: Refused + value: >- + {"detail":"outcome 'rejected' refuses with 'InputRejected': Input was refused"} + - at: 2026-10-03T00:00:02Z + command: entity.executor.Trace + input: {request: '{}'} + outcome: returned + response: {status: ok, value: '["lookup_batch","lookup_record:refused"]'} + - at: 2026-10-03T00:00:03Z + command: entity.executor.Load + input: {request: '{"entity":"invoice","id":"missing"}'} + outcome: returned + response: {status: ok, value: 'null'} + - at: 2026-10-03T00:00:04Z + command: entity.executor.LookupRecord + input: {request: '{"record_id":"refused"}'} + outcome: returned + response: {status: ok, value: 'null'} diff --git a/ess/scenarios/executor/issue-50-malformed-version.yaml b/ess/scenarios/executor/issue-50-malformed-version.yaml new file mode 100644 index 00000000..a21bb4c0 --- /dev/null +++ b/ess/scenarios/executor/issue-50-malformed-version.yaml @@ -0,0 +1,22 @@ +type: ess-scenario/4 +domain: entity.executor +scenario: issue-50-malformed-version +summary: Present malformed definition versions are rejected instead of silently falling back to legacy execution. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity.executor.Execute + input: + request: '{"entity":"invoice","id":"a","definition_version":null,"expected_revision":1,"operation":"Pay","arguments":{},"recording":{"record_id":"bad","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}' + outcome: returned + response: {status: error, error: Deserialize} + - at: 2026-10-03T00:00:01Z + command: entity.executor.Batch + input: + request: '{"key":{"named":"bad"},"actions":[{"kind":"execute","entity":"invoice","id":"a","definition_version":"latest","expected_revision":1,"operation":"Pay","arguments":{},"recording":{"record_id":"bad","recorded_at":"2026-10-03T00:00:00Z","actor":null,"correlation":null,"causation":null}}]}' + outcome: returned + response: {status: error, error: Deserialize} + - at: 2026-10-03T00:00:02Z + command: entity.executor.Trace + input: {request: '{}'} + outcome: returned + response: {status: ok, value: '[]'} diff --git a/ess/scenarios/executor/issue-50-versioned-merge-preserves-proof.yaml b/ess/scenarios/executor/issue-50-versioned-merge-preserves-proof.yaml new file mode 100644 index 00000000..c784adc0 --- /dev/null +++ b/ess/scenarios/executor/issue-50-versioned-merge-preserves-proof.yaml @@ -0,0 +1,47 @@ +domain: entity.executor +scenario: issue-50-versioned-merge-preserves-proof +summary: Explicit version binding retains the selected merge base and all branch tips at the real append boundary. +timeline: +- at: 2026-09-28T00:00:00Z + command: entity.executor.Register + input: + request: '{"definition":{"create":{"emit":null},"entity":"ticket","invariants":[],"lifecycle":{"initial":"open","states":["open","closed"]},"operations":{"close":{"arguments":{"additional_fields":false,"fields":{}},"emits":[],"preconditions":[],"set":{},"transitions":[{"from":"open","to":"closed"}]},"touch":{"arguments":{"additional_fields":false,"fields":{}},"emits":[],"preconditions":[],"set":{},"transitions":[{"from":"open","to":"open"}]}},"projections":{},"schema":{"additional_fields":false,"fields":{"title":{"acyclic":null,"additional_properties":false,"entity":null,"inverse":null,"items":null,"max":null,"max_length":null,"min":null,"min_length":null,"properties":{},"required":true,"type":"string","values":[]}}},"version":1}}' + outcome: returned + response: + error: '' + status: ok +- at: 2026-09-28T00:00:01Z + command: entity.executor.SupplyHistory + input: + request: '{"history":{"origin":"genesis","records":[{"entry":{"decision":{"envelope":{"actor":null,"causation":null,"correlation":null,"record":{"changed":{"title":"a"},"command":{"command":"create","fields":{"title":"a"}},"definition":{"create":{"emit":null},"entity":"ticket","invariants":[],"lifecycle":{"initial":"open","states":["open","closed"]},"operations":{"close":{"arguments":{"additional_fields":false,"fields":{}},"emits":[],"preconditions":[],"set":{},"transitions":[{"from":"open","to":"closed"}]},"touch":{"arguments":{"additional_fields":false,"fields":{}},"emits":[],"preconditions":[],"set":{},"transitions":[{"from":"open","to":"open"}]}},"projections":{},"schema":{"additional_fields":false,"fields":{"title":{"acyclic":null,"additional_properties":false,"entity":null,"inverse":null,"items":null,"max":null,"max_length":null,"min":null,"min_length":null,"properties":{},"required":true,"type":"string","values":[]}}},"version":1},"entity":"ticket","events":[],"from_state":null,"id":"a","result":{"entity":"ticket","fields":{"title":"a"},"id":"a","lifecycle_state":"open","revision":1,"version":1},"revision":1,"to_state":"open"},"record_id":"root","recorded_at":"2026-09-28T00:00:00Z"},"instance":{"entity":"ticket","fields":{"title":"a"},"id":"a","lifecycle_state":"open","revision":1,"version":1}}},"expect":null,"lineage":{"digest":"root","parents":[]},"position":{"store":0,"subject":0},"receipt":{"batch_key":{"single_record":"root"},"kind":"decision","member_index":0,"position":{"store":0,"subject":0},"record_id":"root","revision":1,"subject":{"entity":"ticket","id":"a"}}},{"entry":{"decision":{"envelope":{"actor":null,"causation":null,"correlation":null,"record":{"changed":{},"command":{"arguments":{},"command":"execute","operation":"touch"},"definition":{"create":{"emit":null},"entity":"ticket","invariants":[],"lifecycle":{"initial":"open","states":["open","closed"]},"operations":{"close":{"arguments":{"additional_fields":false,"fields":{}},"emits":[],"preconditions":[],"set":{},"transitions":[{"from":"open","to":"closed"}]},"touch":{"arguments":{"additional_fields":false,"fields":{}},"emits":[],"preconditions":[],"set":{},"transitions":[{"from":"open","to":"open"}]}},"projections":{},"schema":{"additional_fields":false,"fields":{"title":{"acyclic":null,"additional_properties":false,"entity":null,"inverse":null,"items":null,"max":null,"max_length":null,"min":null,"min_length":null,"properties":{},"required":true,"type":"string","values":[]}}},"version":1},"entity":"ticket","events":[],"from_state":"open","id":"a","result":{"entity":"ticket","fields":{"title":"a"},"id":"a","lifecycle_state":"open","revision":2,"version":1},"revision":2,"to_state":"open"},"record_id":"left","recorded_at":"2026-09-28T00:00:00Z"},"instance":{"entity":"ticket","fields":{"title":"a"},"id":"a","lifecycle_state":"open","revision":2,"version":1}}},"expect":1,"lineage":{"digest":"left","parents":["root"]},"position":{"store":1,"subject":1},"receipt":{"batch_key":{"single_record":"left"},"kind":"decision","member_index":0,"position":{"store":1,"subject":1},"record_id":"left","revision":2,"subject":{"entity":"ticket","id":"a"}}},{"entry":{"decision":{"envelope":{"actor":null,"causation":null,"correlation":null,"record":{"changed":{},"command":{"arguments":{},"command":"execute","operation":"close"},"definition":{"create":{"emit":null},"entity":"ticket","invariants":[],"lifecycle":{"initial":"open","states":["open","closed"]},"operations":{"close":{"arguments":{"additional_fields":false,"fields":{}},"emits":[],"preconditions":[],"set":{},"transitions":[{"from":"open","to":"closed"}]},"touch":{"arguments":{"additional_fields":false,"fields":{}},"emits":[],"preconditions":[],"set":{},"transitions":[{"from":"open","to":"open"}]}},"projections":{},"schema":{"additional_fields":false,"fields":{"title":{"acyclic":null,"additional_properties":false,"entity":null,"inverse":null,"items":null,"max":null,"max_length":null,"min":null,"min_length":null,"properties":{},"required":true,"type":"string","values":[]}}},"version":1},"entity":"ticket","events":[],"from_state":"open","id":"a","result":{"entity":"ticket","fields":{"title":"a"},"id":"a","lifecycle_state":"closed","revision":2,"version":1},"revision":2,"to_state":"closed"},"record_id":"right","recorded_at":"2026-09-28T00:00:00Z"},"instance":{"entity":"ticket","fields":{"title":"a"},"id":"a","lifecycle_state":"closed","revision":2,"version":1}}},"expect":1,"lineage":{"digest":"right","parents":["root"]},"position":{"store":2,"subject":2},"receipt":{"batch_key":{"single_record":"right"},"kind":"decision","member_index":0,"position":{"store":2,"subject":2},"record_id":"right","revision":2,"subject":{"entity":"ticket","id":"a"}}}],"subject":{"entity":"ticket","id":"a"}}}' + outcome: returned + response: + error: '' + status: ok +- at: 2026-09-28T00:00:02Z + command: entity.executor.Batch + input: + request: '{"actions":[{"arguments":{},"entity":"ticket","expected_revision":2,"first":"left","id":"a","kind":"merge","definition_version":1,"operation":"touch","recording":{"actor":null,"causation":null,"correlation":null,"record_id":"merge","recorded_at":"2026-09-28T00:00:00Z"}}],"key":{"single_record":"merge"}}' + outcome: returned + response: + error: Backend + status: error +- at: 2026-09-28T00:00:03Z + command: entity.executor.CapturedMerge + input: + request: '{}' + outcome: returned + response: + error: '' + status: ok + value: '[{"merge":{"base":{"entity":"ticket","fields":{"title":"a"},"id":"a","lifecycle_state":"open","revision":2,"version":1},"heads":["left","right"]},"result":{"entity":"ticket","fields":{"title":"a"},"id":"a","lifecycle_state":"open","revision":3,"version":1}}]' +- at: 2026-09-28T00:00:04Z + command: entity.executor.LookupRecord + input: + request: '{"record_id":"merge"}' + outcome: returned + response: + error: '' + status: ok + value: 'null' +type: ess-scenario/4 diff --git a/ess/scenarios/executor/refusal-ignores-success-fulfillments.yaml b/ess/scenarios/executor/refusal-ignores-success-fulfillments.yaml new file mode 100644 index 00000000..d078c298 --- /dev/null +++ b/ess/scenarios/executor/refusal-ignores-success-fulfillments.yaml @@ -0,0 +1,46 @@ +type: ess-scenario/4 +domain: entity.executor +scenario: refusal-ignores-success-fulfillments +summary: An input guard preserves its declared refusal despite success fulfillment keys, without recording a decision. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity.executor.Register + input: + request: '{"definition":{"entity":"invoice","version":1,"semantics":"service/3","schema":{"fields":{"issued_at":{"type":"string","required":true},"note":{"type":"string"}}},"lifecycle":{"initial":"Draft","states":["Draft"]},"operations":{"Issue":{"arguments":{"fields":{"accept":{"type":"boolean","required":true}}},"outcomes":[{"name":"rejected","when":{"eq":["$args.accept",false]},"refuses":{"error":"NotAccepted","message":"Invoice cannot be issued"}},{"name":"issued","effect":"updates","fulfills":{"issued_at":{"actions":"required"},"note":{"actions":"optional"}}}]}}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity.executor.Create + input: + request: '{"entity":"invoice","id":"i-1","version":1,"fields":{"issued_at":"pending","note":"remove"},"recording":{"record_id":"r-1","recorded_at":"2026-10-03T00:00:00Z","correlation":null,"causation":null,"actor":null}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:02Z + command: entity.executor.Execute + input: + request: '{"entity":"invoice","id":"i-1","expected_revision":1,"operation":"Issue","arguments":{"accept":false},"fulfillments":{"issued_at":{"set":{"value":"must not be stored"}},"note":"remove"},"recording":{"record_id":"r-2","recorded_at":"2026-10-03T00:00:00Z","correlation":null,"causation":null,"actor":null}}' + outcome: returned + response: + status: error + error: Refused + value: >- + {"detail":"outcome 'rejected' refuses with 'NotAccepted': Invoice cannot be issued"} + - at: 2026-10-03T00:00:03Z + command: entity.executor.Load + input: {request: '{"entity":"invoice","id":"i-1"}'} + outcome: returned + response: + status: ok + value: '{"entity":"invoice","fields":{"issued_at":"pending","note":"remove"},"id":"i-1","lifecycle_state":"Draft","revision":1,"version":1}' + - at: 2026-10-03T00:00:04Z + command: entity.executor.LookupReceipt + input: {request: '{"record_id":"r-2"}'} + outcome: returned + response: {status: ok, value: 'null'} + - at: 2026-10-03T00:00:05Z + command: entity.executor.HistoryCoordinates + input: {request: '{"entity":"invoice","id":"i-1"}'} + outcome: returned + response: + status: ok + value: '[{"events":[],"kind":"decision","receipt":{"batch_key":{"single_record":"r-1"},"kind":"decision","member_index":0,"position":{"store":0,"subject":0},"record_id":"r-1","revision":1,"subject":{"entity":"invoice","id":"i-1"}},"record_id":"r-1","revision":1}]' diff --git a/ess/scenarios/executor/subject-refusal-ignores-success-fulfillments.yaml b/ess/scenarios/executor/subject-refusal-ignores-success-fulfillments.yaml new file mode 100644 index 00000000..8d8d5375 --- /dev/null +++ b/ess/scenarios/executor/subject-refusal-ignores-success-fulfillments.yaml @@ -0,0 +1,46 @@ +type: ess-scenario/4 +domain: entity.executor +scenario: subject-refusal-ignores-success-fulfillments +summary: A stored-field guard preserves its declared refusal despite success fulfillment keys, without recording a decision. +timeline: + - at: 2026-10-03T00:00:00Z + command: entity.executor.Register + input: + request: '{"definition":{"entity":"invoice","version":1,"semantics":"service/3","schema":{"fields":{"issued_at":{"type":"string","required":true},"note":{"type":"string"}}},"lifecycle":{"initial":"Draft","states":["Draft"]},"operations":{"Issue":{"arguments":{"fields":{"accept":{"type":"boolean","required":true}}},"outcomes":[{"name":"rejected","when":{"eq":["$fields.issued_at","pending"]},"refuses":{"error":"NotAccepted","message":"Invoice cannot be issued"}},{"name":"issued","effect":"updates","fulfills":{"issued_at":{"actions":"required"},"note":{"actions":"optional"}}}]}}}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:01Z + command: entity.executor.Create + input: + request: '{"entity":"invoice","id":"i-1","version":1,"fields":{"issued_at":"pending","note":"remove"},"recording":{"record_id":"r-1","recorded_at":"2026-10-03T00:00:00Z","correlation":null,"causation":null,"actor":null}}' + outcome: returned + response: {status: ok} + - at: 2026-10-03T00:00:02Z + command: entity.executor.Execute + input: + request: '{"entity":"invoice","id":"i-1","expected_revision":1,"operation":"Issue","arguments":{"accept":false},"fulfillments":{"issued_at":{"set":{"value":"must not be stored"}},"note":"remove"},"recording":{"record_id":"r-2","recorded_at":"2026-10-03T00:00:00Z","correlation":null,"causation":null,"actor":null}}' + outcome: returned + response: + status: error + error: Refused + value: >- + {"detail":"outcome 'rejected' refuses with 'NotAccepted': Invoice cannot be issued"} + - at: 2026-10-03T00:00:03Z + command: entity.executor.Load + input: {request: '{"entity":"invoice","id":"i-1"}'} + outcome: returned + response: + status: ok + value: '{"entity":"invoice","fields":{"issued_at":"pending","note":"remove"},"id":"i-1","lifecycle_state":"Draft","revision":1,"version":1}' + - at: 2026-10-03T00:00:04Z + command: entity.executor.LookupReceipt + input: {request: '{"record_id":"r-2"}'} + outcome: returned + response: {status: ok, value: 'null'} + - at: 2026-10-03T00:00:05Z + command: entity.executor.HistoryCoordinates + input: {request: '{"entity":"invoice","id":"i-1"}'} + outcome: returned + response: + status: ok + value: '[{"events":[],"kind":"decision","receipt":{"batch_key":{"single_record":"r-1"},"kind":"decision","member_index":0,"position":{"store":0,"subject":0},"record_id":"r-1","revision":1,"subject":{"entity":"invoice","id":"i-1"}},"record_id":"r-1","revision":1}]'