diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d70b9f2..006a07a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,7 +32,7 @@ jobs:
python3 -m unittest discover -s scripts/tests -p 'test_*.py'
lean-proof:
- name: Lean assurance laws
+ name: Lean assurance and SDLC design laws
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
@@ -63,9 +63,18 @@ jobs:
echo "ELAN_HOME=$ELAN_HOME" >> "$GITHUB_ENV"
echo "$ELAN_HOME/bin" >> "$GITHUB_PATH"
- - name: Build assurance proof library
+ - name: Build assurance and SDLC proof libraries
working-directory: proof/lean
- run: lake build LambdaAitiaModuleAssuranceProof
+ run: lake build LambdaAitiaModuleAssuranceProof LambdaAitiaModuleSdlcProof
+
+ - name: Reject sorry axioms in exported laws
+ working-directory: proof/lean
+ shell: bash
+ run: |
+ set -euo pipefail
+ output="$(lake env lean AxiomAudit.lean)"
+ printf '%s\n' "$output"
+ ! grep -q 'sorryAx' <<< "$output"
- name: Save Lean cache
if: steps.lean-cache.outputs.cache-hit != 'true'
@@ -76,6 +85,34 @@ jobs:
proof/lean/.lake
key: lambda-aitia-lean-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('proof/lean/lean-toolchain', 'proof/lean/lakefile.lean', 'proof/lean/lake-manifest.json') }}
+ quint-model:
+ name: Quint lifecycle, retirement and ingress contracts
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ steps:
+ - name: Checkout Lambda Aitia
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+
+ - name: Set up Node for pinned Quint
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ node-version: '22'
+
+ - name: Set up Java for Quint TLC backend
+ uses: actions/setup-java@d7793b545071e98d581d3bf084a51c3213318a07 # v4.9.0
+ with:
+ distribution: temurin
+ java-version: '21'
+
+ - name: Install locked Quint and check all 25 model contracts
+ env:
+ AITIA_QUINT_BIN: ${{ github.workspace }}/proof/quint/node_modules/.bin/quint
+ QUINT_HOME: ${{ runner.temp }}/lambda-aitia-quint
+ run: |
+ npm ci --prefix proof/quint
+ mkdir -p "$QUINT_HOME"
+ python3 proof/quint/check_contract.py
+
scheme-runtime:
name: Scheme, integration and Python runtime
runs-on: ubuntu-latest
@@ -89,7 +126,7 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: tao3k/poo-flow
- ref: da492eea00a7dab502cfb4c7586edefbba96bbec
+ ref: 3a6330f9135b7e5d7a96f9c2de7b5fb90e9ad399
path: poo-flow
submodules: false
@@ -144,6 +181,35 @@ jobs:
working-directory: poo-flow
run: bazelisk run @local_gerbil//:native_scheme_env -- bash -lc 'cd "$BUILD_WORKSPACE_DIRECTORY/packages/lambda-aitia" && just check-semantic'
+ - name: Bind Quint and Lean sources to the retirement POO contract
+ working-directory: poo-flow
+ run: bazelisk run @local_gerbil//:native_scheme_env -- bash -lc 'cd "$BUILD_WORKSPACE_DIRECTORY/packages/lambda-aitia" && just test-scheme-atomic formal retirement-binding-test.ss'
+
+ - name: Execute retirement ingress controls against Scheme Host
+ working-directory: poo-flow
+ run: bazelisk run @local_gerbil//:native_scheme_env -- bash -lc 'cd "$BUILD_WORKSPACE_DIRECTORY/packages/lambda-aitia" && just test-scheme-atomic formal retirement-execution-test.ss'
+
+ - name: Set up Node for pinned Quint correspondence traces
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ node-version: '22'
+
+ - name: Replay Quint states and unsafe-model controls through Scheme
+ working-directory: poo-flow
+ run: |
+ npm ci --prefix packages/lambda-aitia/proof/quint
+ bazelisk run @local_gerbil//:native_scheme_env -- bash -lc 'cd "$BUILD_WORKSPACE_DIRECTORY/packages/lambda-aitia" && just check-quint-scheme'
+
+ - name: Set up Java for complete lifecycle state graph
+ uses: actions/setup-java@d7793b545071e98d581d3bf084a51c3213318a07 # v4.9.0
+ with:
+ distribution: temurin
+ java-version: '21'
+
+ - name: Replay every reachable lifecycle state and authorization delivery fault
+ working-directory: poo-flow
+ run: bazelisk run @local_gerbil//:native_scheme_env -- bash -lc 'cd "$BUILD_WORKSPACE_DIRECTORY/packages/lambda-aitia" && just check-lifecycle-scheme'
+
- name: Run SDLC and GitOps integration
working-directory: poo-flow
run: bazelisk run @local_gerbil//:native_scheme_env -- bash -lc 'cd "$BUILD_WORKSPACE_DIRECTORY/packages/lambda-aitia" && just test-integration'
diff --git a/.gitignore b/.gitignore
index aa2c3dd..524550e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,8 @@ version.ss
.cache/
.bin/
.lake/
+_apalache-out/
+proof/quint/node_modules/
__pycache__/
*.py[cod]
*.so
diff --git a/README.org b/README.org
index b2db2b3..a143e52 100644
--- a/README.org
+++ b/README.org
@@ -1,14 +1,18 @@
# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
-#+title: Lambda Aitia — causal assurance as executable knowledge
+#+title: Lambda Aitia — causal assurance across the software lifecycle
#+startup: overview
* Purpose
-Lambda Aitia is the software and SDLC causal-assurance contribution for the POO
-Flow ecosystem. It also owns the software Architecture Decision Record (ADR)
-module migrated from Lambda Episteme. It answers five questions about an
+Lambda Aitia organizes software engineering around the complete SDLC:
+requirements, design, algorithms and implementation strategies, verification
+and testing, delivery, operation, maintenance and retirement. Causal Assurance
+connects the justification of engineering decisions across that lifecycle.
+POO Flow supplies the reusable composition, temporal/causal and runtime
+mechanisms. Aitia also owns the software Architecture Decision Record (ADR)
+module migrated from Lambda Episteme. It answers five questions about an
engineering conclusion:
1. What exactly is claimed?
@@ -18,27 +22,30 @@ engineering conclusion:
5. Which decisions and real effects must remain blocked until the resulting
obligations are discharged again?
-The project manages the justification and invalidation of conclusions. It is
-not a database of green verification results and it is not a generic knowledge
-graph.
+The project helps form, compare and revise design decisions, then retains the
+evidence explaining why their guarantees remain applicable. Requirements lead
+to explicit assumptions, design alternatives, responsibilities, state ownership
+and conditional guarantees. Algorithm choices and code implement those
+decisions; tests and runtime observations can challenge them and reopen design.
+
+System behavior trajectories and design/assurance evolution trajectories are
+linked through evidence. Observed executions, model traces, hypotheses and
+counterfactual alternatives preserve their distinct scope. Temporal order and
+graph reachability alone cannot establish a causal explanation.
+[[file:docs/rfcs/0004-sdlc-design-and-causal-trajectories.org][RFC 0004]] defines this lifecycle design and its pending acceptance gates.
* Place in the ecosystem
-#+begin_example
-lambda-aitia
- Software/SDLC, ADR, NASA and GitOps sources
- claims, assumptions, typed relations, obligations, evidence,
- invalidation and assurance planning
- ^
- |
-lambda-episteme
- optional non-SDLC vertical knowledge and decision-kind/ontology sources
- |
- v
-POO Flow
- POO-native composition, functional flow, graph substrate,
- verifier/runtime handoff and effect boundary
-#+end_example
+#+begin_export mermaid
+flowchart TD
+ App["Application
explicit composition"] --> A["Lambda Aitia
SDLC, design and Causal Assurance"]
+ App --> E["Lambda Episteme
optional vertical knowledge"]
+ E -->|source facts when composed| A
+ A -->|consumes public library| P["POO Flow
composition, graph, temporal / causal mechanisms and Runtime"]
+ E -->|consumes public library| P
+ A --> Py["Aitia Python Runtime
owned-byte C ABI, tools and observations"]
+ Py -->|uses owned runtime boundaries| P
+#+end_export
This is a conceptual flow, not permission to collapse repository ownership.
POO Flow core never imports a concrete Lambda contribution. Lambda Aitia may
@@ -49,23 +56,26 @@ Flow. Mounting this Git
submodule does not import a module, activate a Profile, run a verifier, grant a
capability, authorize a decision or execute an effect.
-The production Python SDLC Runtime is maintained entirely under
+The production Aitia Python Runtime is maintained entirely under
=bindings/python= in this repository; it is not an example. POO Flow's
=packages/python-runtime= remains the generic graph execution and lifecycle
mechanism and does not own Aitia semantics. Aitia Scheme is compiled AOT
behind an owned-byte C ABI. The Python project validates typed lifecycle
receipts and exposes them to downstream systems without reimplementing Scheme
-decisions.
+decisions. The Python Runtime serves Aitia capabilities throughout the SDLC;
+the existing =SdlcRuntime.plan()= is one implemented assurance-flow projection.
+AnyIO is declared as its runtime-wide concurrency foundation; async execution
+and Host admission remain separate implementation gates. Generic Graph/DAG and
+FunFlow execution mechanisms remain owned by POO Flow. MRR relation-query and
+lineage integration is a later design direction, outside the current Python
+implementation stage.
* Core model
The initial semantic vocabulary is deliberately small:
-#+begin_example
-Artifact Claim Assumption Observation
-Event Action Obligation Evidence
-Counterexample Finding Decision Effect
-#+end_example
+=Artifact=, =Claim=, =Assumption=, =Observation=, =Event=, =Action=,
+=Obligation=, =Evidence=, =Counterexample=, =Finding=, =Decision= and =Effect=.
Relations are typed by plane. A generic =edge(from, to)= is not part of the
public model.
@@ -87,15 +97,14 @@ authority.
The first vertical slice is one fail-closed lifecycle:
-#+begin_example
-source revision changes
- -> structural impact is computed
- -> affected assumptions and claims are selected
- -> dependent evidence becomes stale
- -> new obligations are planned
- -> the old decision loses authority
- -> the associated effect remains blocked
-#+end_example
+#+begin_export mermaid
+flowchart TD
+ C["Source revision
changed"] --> I["Structural impact
affected assumptions and claims identified"]
+ I --> S["Dependent support
stale"]
+ S --> O["New obligations
pending"]
+ S --> D["Old decision
invalid for the changed closure"]
+ D --> E["Associated effect
blocked"]
+#+end_export
The slice succeeds only when receipts bind the subject, source revision,
assumption set, claim, obligation, producer identity, tool identity, artifact
@@ -114,17 +123,18 @@ evaluate that model.
** Thirty-second compression
-Spec-first Agent work asks an Agent to follow a better specification. Specula
-uses Agents to derive and validate TLA+ models from system code so that deep
-concurrency bugs can be found and reproduced. Lambda Aitia addresses the next
-system-level question: after RFCs, code, formal models, proofs, policies, tests
-and runtime observations all exist, which exact conclusion is still justified
-for the current revision, what change invalidated it, which obligations must be
-rerun, and why must an Agent effect remain blocked?
+Aitia uses existing verification methods to help choose and revise software
+designs throughout the SDLC. It connects requirements, assumptions, guarantees,
+RFC/ADR decisions, implementation strategies and observed behavior. A new
+finding can change the design, invalidate earlier support and create new
+obligations with an explanation path.
-Aitia therefore does not compete with Lean, TLA+, TLC, Cedar, Specula or POO
-Flow. It binds their distinct artifacts into a causal-assurance lifecycle
-without pretending that one tool's success proves the others' propositions.
+Specula already derives and validates TLA+ models, reproduces bugs and supports
+incremental verification. Aitia should reuse applicable tools, models, harnesses
+and checks while evaluating its own contribution: better-founded design choices,
+composition obligations and traceable changes in engineering justification.
+The full Specula application is optional; an upstream component or script can
+be reused without handing it ownership of Aitia's lifecycle.
** Related work and project-position matrix
@@ -132,10 +142,11 @@ without pretending that one tool's success proves the others' propositions.
|-----------------+----------------+---------------+----------------------------+--------------|
| POO Flow Agentic Control Plane RFC suite | organization Bundle, Agent control plane, proof/evidence chain and effect token | establishes reusable mechanism, ownership and authority boundaries | intentionally does not own Aitia's Software/SDLC claims or assurance policy | POO Flow is the full public mechanism Library on which Aitia is built |
| Agent RFC / specification-first Agent development | human-reviewed intent, constraints and implementation plan | gives Agents an explicit architecture and acceptance target instead of an ad hoc prompt | a document does not establish current evidence, invalidation closure, authorization or execution | Aitia turns RFC clauses and revisions into typed claims, threats and obligations |
-| [[https://specula.info/][Specula]] ([[https://arxiv.org/abs/2607.25333][paper]]) | code-derived TLA+ specification, invariants, traces and reproduced bugs | autonomous specification generation, model checking, trace alignment and concrete bug reproduction | concentrates on discovering implementation/model violations; it is not a cross-tool evidence, policy and effect-authority ledger | a Specula run can satisfy Aitia TLA+/replay obligations when its artifacts are identity-bound and admitted |
+| [[https://github.com/specula-org/Specula][Specula]] ([[https://arxiv.org/abs/2607.25333][paper]]) | code-derived models, invariants, traces and reproduced bugs | specification generation, model checking, replay and incremental verification | Aitia must demonstrate additional value in design choices, composition obligations and changing justification | reuse qualified tools and assets under POO composition; the complete application is optional |
+| [[https://github.com/specula-org/specula-case-studies][Specula Case Studies]] | archived models, harnesses, traces and experiment records | concrete engineering assets and known-fault reproduction material | each selected record needs provenance, license and replay qualification | use selected assets as Recipe inputs; replay success is not a new discovery |
| [[https://arxiv.org/abs/2605.02454][Causal Software Engineering]] | lifecycle-wide causal models, assumptions and counterfactual diagnosis | provides the broader research vision for causal reasoning throughout software engineering | a vision and roadmap do not by themselves define Aitia's POO objects, revision receipts or Runtime gate | Aitia is an executable contribution pursuing this direction with typed relation planes and invalidation |
| Conventional CI/CD and test dashboards | jobs, statuses, logs and deployment stages | repeatable automation and immediate regression signals | a green result usually does not encode the complete subject, assumption, revision, causal support or downstream invalidation graph | Aitia consumes exact test/build receipts as evidence rather than treating green status as authority |
-| Lean, TLA+/TLC and Cedar used independently | theorem, checked state space or policy decision | strong, specialized reasoning inside a clearly defined formal boundary | none alone proves model-code alignment, evidence freshness, current authorization and effect completion | Aitia keeps the propositions separate and joins only digest-bound admitted receipts |
+| Lean, Quint/TLC and Cedar used independently | theorem, checked state space or policy decision | strong, specialized reasoning inside a clearly defined formal boundary | none alone proves model-code alignment, evidence freshness, current authorization and effect completion | Aitia keeps the propositions separate and joins only digest-bound admitted receipts |
The comparison is about responsibility rather than feature count. Aitia's
distinctive unit is the current, revision-bound justification of a conclusion
@@ -148,7 +159,7 @@ The threat model covers failures that ordinary pass/fail CI cannot distinguish:
- stale evidence is replayed after source, policy, assumption or tool changes;
- a cached Agent decision survives invalidation or revocation;
-- an RFC claim, Lean statement, TLA+ model and Cedar policy refer to different
+- an RFC claim, Lean statement, Quint model and Cedar policy refer to different
revisions of the supposed same subject;
- tool exit success is admitted without exact input, environment and artifact
bindings;
@@ -169,32 +180,45 @@ different propositions and trusted boundaries:
| Agent RFC and ADR | intent, vocabulary, trust boundary, decisions and required obligations | that an implementation, policy or runtime matches the documents |
| Aitia Assurance Graph | exact subject/revision bindings, typed support, invalidation, unresolved frontier and verification plan | temporal safety, authorization or effect execution |
| Lean | general laws such as freshness, monotone invalidation, modality preservation, no stale authority and composition obligations | that the current deployment supplied fresh facts or executed an effect |
-| TLA+/TLC | bounded lifecycle transitions and adverse interleavings across change, invalidate, plan, verify, admit, decide, authorize and effect | universal theorem proof or production authorization |
+| Quint/TLC | bounded lifecycle transitions and adverse interleavings across change, invalidate, plan, verify, admit, decide, authorize and effect | universal theorem proof or production authorization |
| Cedar | an allow/deny decision for one identity-bound principal, action, resource, context, policy and entity snapshot | evidence freshness, proof construction, runtime conformance or external success |
| Runtime Host | one bounded execution attempt and its typed execution receipt | that missing proof or authorization may be inferred |
-The intended evidence flow is:
-
-#+begin_example
-Agent RFC / ADR / source revisions
- -> Aitia snapshot and threat/impact graph
- -> invalidation and typed verification obligations
- -> Lean proof receipts + TLA+/TLC model-check receipts + test/replay evidence
- -> evidence admission against the same snapshot
- -> Cedar projection and identity-bound authorization decision
- -> one bounded POO Flow Runtime execution
- -> execution receipt and post-effect evidence
-#+end_example
-
-Every arrow is digest- and revision-bound. Lean cannot replace Cedar, Cedar
-cannot replace TLA+, a TLC completion cannot replace runtime evidence, and a
+The intended evidence workflow names semantic states, not implementation
+completion. RFC 0004 contains the full SDLC workflow and its design feedback.
+
+#+begin_export mermaid
+flowchart TD
+ D["RFC / ADR and sources
declared"] --> S["Snapshot / impact graph
bound"]
+ S --> O["Verification obligations
pending"]
+ O --> V["Proof, model check, test / replay
candidate results"]
+ V --> A{"Trusted admission valid?"}
+ A -->|yes| C["Scoped support
admitted and current"]
+ C --> G{"Effect gate allows?"}
+ G -->|yes| X["Bounded Runtime attempt
authorized"]
+ X --> R["Receipt and observations
outcome recorded"]
+ R --> F["Lifecycle reassessment
design review when challenged"]
+ F --> D
+ A -->|no / unknown| U["Unresolved / rejected
affected effect blocked"]
+ U --> O
+ G -->|no / unknown| B["Effect blocked
review / revalidation required"]
+ B --> O
+ C -->|changed / expired| B
+#+end_export
+
+Admission checks the subject, revision and cut. The effect gate requires a
+current decision, Cedar authorization and Runtime enforcement. Every arrow is
+digest- and revision-bound. Lean cannot replace Cedar, Cedar
+cannot replace Quint, a TLC completion cannot replace runtime evidence, and a
Runtime success cannot retroactively prove the RFC. Unknown, conflicted,
stale, mismatched or unavailable inputs fail closed and remain visible in the
receipt graph.
-The current repository has executable snapshot/invalidation and modular Lean
-evidence. The complete TLA+ lifecycle, Cedar admission and Runtime effect gate
-remain explicit roadmap phases; their absence is not represented as closure.
+The current repository has executable snapshot/invalidation, pure planning,
+modular Lean laws, bounded Quint lifecycle/retirement/ingress models and known-fault Python replay.
+The connected design/trajectory experiment, upstream Recipe qualification,
+Python-to-Host evidence admission and Runtime effect gate remain open. See
+[[file:ROADMAP.org][ROADMAP]] for phase-specific evidence and limits.
See [[file:docs/rfcs/0001-causal-assurance-core.org][RFC 0001]],
[[file:docs/rfcs/0002-snapshot-and-invalidation-kernel.org][RFC 0002]],
[[file:docs/rfcs/0003-composition-value-interface.org][RFC 0003]] and
@@ -202,11 +226,11 @@ See [[file:docs/rfcs/0001-causal-assurance-core.org][RFC 0001]],
* Project rules
-- Public extension APIs are POO-native. The ordinary =aitia.ss= surface selects
- the maintained =aitia= Composition and fills bounded Project slots; its macro
- lowers directly to those POO values. Prototype, C3 and slot-transform
- operators are exposed only when a user needs an advanced override. Neither
- layer uses raw alist DSLs or ad hoc patch languages.
+- Public extension APIs are POO-native. The maintained =aitia= Case now uses
+ POO Flow's =user-composition=, =compose profiles= and =use-module=.
+ The former no-import =aitia.ss= convenience is an open RFC 0003 UX gate,
+ not a supported loader in the current Profile algebra. Advanced overrides
+ use POO slots; neither path introduces an alist or patch-language DSL.
- Functional transformations are values first. Side effects live behind an
explicit POO Flow verifier or runtime handoff.
- Native Scheme tests are never heap-unbounded: the Aitia Testing Interface
@@ -238,6 +262,14 @@ The machine-readable declaration and complete texts are maintained in
- [[file:docs/rfcs/0003-composition-value-interface.org][RFC 0003 — Composition Value Interface]] defines the Jsonnet-style single-value
downstream file, the official =aitia= Composition and the advanced POO
override boundary.
+- [[file:docs/rfcs/0004-sdlc-design-and-causal-trajectories.org][RFC 0004 — SDLC Design, Causal Assurance and Trajectories]] defines the
+ complete lifecycle framework, design leverage, reuse contracts and future
+ MRR boundary, with implementation acceptance still pending.
+- [[file:docs/rfcs/0005-org-native-engineering-workspace.org][RFC 0005 — Org-native engineering workspace]] defines source-bound Agent
+ review, POO Flow dependency ownership and the unresolved package cycle.
+ The [[file:user-interface/workspaces/job-executor.org][maintained job-executor workspace]] is the first native authoring surface.
+ Qualification must use Scheme-owned declarations and the Rowan/AOT consumer;
+ earlier legacy CLI lint/query results do not establish this integration.
- [[file:docs/adrs/README.org][ADR contract and template]] defines standardized bounded decisions, lifecycle,
RFC links and the non-authorizing assurance projection.
- [[file:docs/adrs/0007-consume-poo-flow-public-library.org][ADR 0007]] establishes POO Flow as Aitia's reusable public mechanism library.
@@ -245,8 +277,8 @@ The machine-readable declaration and complete texts are maintained in
- [[file:docs/plans/software-sdlc-integration-inventory.org][Software/SDLC integration inventory]] classifies the existing implementation as
keep, reference, generalize or later migrate.
-The repository has Phase 0 and Phase 1 evidence-partial work plus an active
-Phase 2 snapshot/invalidation slice. ABI revision 2 and =SdlcRuntime.plan()=
-form the first production Python runtime contract for the complete eight-stage
-SDLC DAG. That plan remains inert: executable stage receipts, release
-authority and completed effects are not claimed yet.
+Phase 2 snapshot/invalidation and Phase 3 pure planning have bounded completion
+receipts; later phases retain their explicit gaps. ABI revision 2 and
+=SdlcRuntime.plan()= expose the eight-stage assurance/execution DAG. That DAG
+is a protocol used within the full SDLC, and its plan remains inert: executable
+stage receipts, release authority and completed effects are not inferred.
diff --git a/ROADMAP.org b/ROADMAP.org
index df0eec8..fd61e92 100644
--- a/ROADMAP.org
+++ b/ROADMAP.org
@@ -4,6 +4,62 @@
#+title: Lambda Aitia Roadmap
#+startup: overview
+* Lifecycle framework and current priority
+
+The complete SDLC is the product framework: requirements, software design,
+algorithm and implementation strategies, verification/testing, delivery,
+operation, maintenance and retirement. These activities exchange observations
+and reopen earlier decisions. The numbered phases below are implementation
+milestones for that framework, not the lifecycle experienced by a project.
+
+[[file:docs/rfcs/0004-sdlc-design-and-causal-trajectories.org][RFC 0004]] makes software design an early leverage point and Causal Assurance
+the connection across the lifecycle. The next product qualification must link
+requirements, at least two design alternatives, assumptions and guarantees to
+real execution trajectories, evidence, a revised ADR and renewed obligations.
+Recipe execution and tool integration contribute to that connected experiment.
+
+The agreed lifecycle Impact design in RFC 0004 covers capability introduction,
+change and retirement across historical assets/commitments, current code and
+operation, and future work. POO Flow owns generic analysis; Aitia adds SDLC
+meaning. A bounded job-executor retirement case is the next paired Impact
+qualification, not an instruction to remove a production capability. Its
+implementation and execution receipts remain pending.
+
+Reuse POO Flow Temporal Causality and existing upstream verification tools and
+assets. AnyIO serves the entire Aitia Python Runtime. MRR relation queries,
+derivation and lineage are deferred beyond the current Python implementation
+stage; this queue introduces no MRR integration.
+
+This view tracks implementation progress; it is separate from the engineering
+states in RFC 0004's lifecycle workflow.
+
+[[file:docs/rfcs/0005-org-native-engineering-workspace.org][RFC 0005]] adds the maintained [[file:user-interface/workspaces/job-executor.org][Org-native job-executor workspace]]. Its
+Scheme-owned contract and Rowan consumer qualification remain pending; the
+earlier legacy CLI checks do not close this gate. POO Flow owns the intended
+Orgize dependency; direct Scheme package
+installation is blocked by the Orgize → gerbil-parser → POO Flow dependency
+cycle and missing Orgize package entrypoint. Existing generated Cargo artifacts
+can be qualified independently of that source-package cycle. Rust/Rowan runtime
+delivery is the later durable path, not an interim workaround or a prerequisite
+for the current Python stage. The immediate implementation priority remains
+Aitia's production Python Runtime: preserve frozen source observations through
+AnyIO execution and reach the actual Scheme Host admission path. Python must
+not reimplement Org parsing or contract evaluation to bypass the pending
+Orgize boundary. Role projection, source-bound edits and the connected
+lifecycle remain separate gates.
+
+#+begin_export mermaid
+flowchart TD
+ B["Existing bounded foundations
invalidation, planning, formal laws and replay"]
+ B --> D["Pending
design alternatives and upstream qualification"]
+ B --> H["Pending
Python-to-Host admission and async transport"]
+ D --> T["Pending
connected behavior and design trajectories"]
+ H --> T
+ T --> R["Pending
revision, revalidation and comparative evidence"]
+ R --> O["Pending
contract-preserving optimization evidence"]
+ O -. later design .-> M["Deferred
MRR relation queries, derivation and lineage"]
+#+end_export
+
* Completion discipline
Each phase closes only when its source, negative contracts, executable checks
@@ -36,7 +92,7 @@ Deliverables:
applications compose contributions; POO Flow core never imports Aitia.
6. A checked integration inventory covering Lambda Episteme Software
Engineering, SDLC, GitOps and decision knowledge plus POO Flow Graph, Lean,
- TLA+, Cedar and Runtime Host artifacts.
+ Quint, Cedar and Runtime Host artifacts.
Exit gates:
@@ -48,13 +104,15 @@ Exit gates:
parent state;
- mounting the repository performs no activation or effect.
-Current evidence: the production dependency closure, Phase 2 Assurance fixture,
-modular Lean library, integration lane and native Scheme/C/Python package replay
+Current evidence: the independent PR CI, production dependency closure, Phase 2
+Assurance fixture, modular Lean library, integration lane and native
+Scheme/C/Python package replay
from an isolated checkout at exact revisions. The Homebrew Darwin toolchain
adaptation is child-process scoped and does not alter a Nix-selected toolchain
or the parent environment. See
=docs/receipts/2026-09-18-phase2-clean-replay.org=. The remaining Phase 0 work
-is the independent CI/release lane and checked integration inventory.
+is a release lane and executable integration-inventory acceptance. The PR CI
+does not by itself constitute a published release or complete Phase 0.
* Phase 1 — SDLC ownership migration and POO-native semantic kernel
@@ -116,12 +174,20 @@ Deliverables:
Core law:
-#+begin_example
-changed(dependency)
-and reaches(dependency, claim)
-=> not authorized(claim)
-until current obligations are discharged by current evidence
-#+end_example
+#+begin_export mermaid
+stateDiagram-v2
+ state "Support: current" as Current
+ state "Support: stale; affected effect blocked" as Stale
+ state "Obligations: pending" as Pending
+ state "Support: renewed at a new cut" as Renewed
+ Current --> Stale: dependency changes and reaches claim
+ Stale --> Pending: derive current obligations
+ Pending --> Renewed: obligations discharged by current admitted evidence
+ Renewed --> Stale: another relevant change
+#+end_export
+
+Renewed support does not itself authorize an effect; current decision and
+authorization gates remain independently required.
Exit gates:
@@ -133,7 +199,56 @@ Exit gates:
* Phase 3 — Obligation derivation and verification planning
-Status: =planned=
+Status: =complete= for pure planning at the exact source cut in
+=docs/receipts/2026-09-23-phase3-planning-replay.org=. Verifier execution,
+evidence promotion and effect authority remain Phase 4/6 work.
+
+The first executable slice reuses the Phase 2 invalidation receipt to select
+snapshot-bound obligations. A POO-native verification policy declares
+available capabilities, and the resulting POO-native plan records selected
+obligations, capability requirements, separate unknown/conflicted blockers and
+the underlying invalidation witnesses. It is deterministic under inventory
+permutation and cannot claim verifier execution, release authority or runtime
+execution.
+Obligation evidence kinds are closed to proof, model checking, replay,
+unit/integration/native tests, static analysis, authorization differential and
+human review; an unknown kind is rejected at the model boundary.
+Within the affected obligation set, structural =depends-on= relations are
+projected into POO Flow Graph. Its topological order becomes the selected
+request order; a blocked prerequisite blocks dependents, an unplanned
+prerequisite is never assumed discharged, and a dependency cycle records its
+path while blocking only its strongly connected component and downstream
+dependents; an independent acyclic branch retains its own order. The plan
+records every cyclic component as well as one deterministic path witness; it
+still does not attest that any prerequisite has been verified.
+An obligation bound to an older snapshot is blocked rather than silently
+promoted to a replacement obligation. Selection compares snapshot identity,
+revision and a non-circular context digest. The context digest covers the
+canonical snapshot while excluding only obligation binding fields; the final
+snapshot digest still covers those fields. Binding an unbound candidate is an
+explicit pure step, and a previously bound obligation cannot be rebound.
+The pure replacement-requirements receipt traces affected old obligations to
+a changed target snapshot and records blockers for unresolved changes, stale
+source bindings, revised claims or policy, and incomplete targets. It derives
+the need for a new declaration without inventing a new obligation identity
+or copying an old evidence requirement across a changed claim. The explicit
+declaration step accepts a caller-authored, unbound candidate in a target draft
+with a revised claim and support edge; it checks the source derivation and
+canonical snapshots, binds only that fresh candidate through the existing
+snapshot binder, reconstructs the final snapshot, and returns an inert receipt.
+It does not copy evidence, invoke a verifier or grant release authority.
+The explicit verifier-candidate catalog is a set of POO-native inert declarations
+with evidence kinds, capability and priority. A pure explanation receipt
+records the chosen declaration, every rejected candidate and unassigned
+planned obligations; ties use candidate identity only after explicit priority.
+This is declared suitability, not adapter installation, execution or evidence
+admission.
+An explicit structural =composes= relation connects a composite claim to its
+component claims and projects through POO Flow Graph for invalidation. A pure
+composition-requirements receipt demands an independent, claim-declared,
+snapshot-bound obligation for the composite. Component status or evidence
+never supplies it, and even a present direct obligation does not attest that
+verification ran or evidence was admitted.
Deliverables:
@@ -157,27 +272,50 @@ Exit gates:
* Phase 4 — Verifier adapters and evidence admission
-Status: =planned=
+Status: =in-progress= (typed reported outcomes, pure structural eligibility and
+sealed-support checks reuse POO Flow's verification adapter. A configured
+Scheme Host capability now owns adapter, clock, lease, current snapshot source,
+revocation and a separate candidate admission ledger. An exact current
+assurance-edge witness can consume that ledger without promoting the snapshot
+evidence slot. A typed required-support receipt now checks all claim-declared
+obligations and both assurance links at the current Host cut, with explicit
+blockers and no release authority. Full Decision closure, trusted deployment,
+Cedar policy and the Runtime effect gate remain open. A Host-issued preflight
+can now bind an undecided Decision and its dependency edge to that support,
+but cannot produce =allow= or an effect token).
Deliverables:
-1. Adapter contracts for Lean, TLA+/TLC, native tests, static analyzers,
+1. Adapter contracts for Lean, Quint/TLC, native tests, static analyzers,
runtime replay and human review.
2. Evidence envelopes binding producer, tool/version, input artifact digests,
obligation, output digest, subject, revision, scope and validity interval.
-3. Admission rules for successful, failed, timed-out, unavailable and
- indeterminate verifier outcomes.
+3. [~] Admission rules for successful, failed, timed-out, unavailable and
+ indeterminate verifier outcomes. The pure eligibility receipt remains
+ inert. A configured Host may now issue a separate admission for candidate
+ evidence after its verifier succeeds over the exact current snapshot;
+ changing =admission-state= in a node is not the promotion operation.
+ Admission remains subject to Host clock, source invalidation and revocation
+ and cannot authorize release.
+ The normal Host support entrypoint requires an exact POO Flow adapter-issued
+ seal over the current semantic subject; no structural-only public support
+ predicate remains. The low-level under-adapter check is explicitly
+ conditional, not an authorization path. A forged adapter or Host
+ presentation is rejected; proving that application configuration installed
+ the intended Host and that its clock and snapshot source are trusted remains
+ open.
4. Provenance-preserving counterexample and finding ingestion.
5. Replay checks proving presentation mutation cannot forge admitted evidence.
6. [X] An Aitia Scheme-native ABI contract following POO Flow's owned-byte,
caller-release pattern; public Scheme and Python namespaces remain
unversioned while ABI revision is explicit metadata.
-7. [X] An Aitia-owned, uv-managed =bindings/python= production SDLC Runtime
+7. [X] An Aitia-owned, uv-managed =bindings/python= production Python Runtime
with an out-of-line CFFI package. POO Flow's =packages/python-runtime=
remains the generic runtime mechanism, and Python contains no duplicate
Aitia semantics.
-8. [X] ABI revision 2 publishes the canonical eight-stage POO Flow DAG as an
- inert typed =SdlcFlowPlan=; it does not claim authorization or execution.
+8. [X] ABI revision 2 publishes the eight-stage assurance/execution protocol as
+ an inert typed =SdlcFlowPlan=; this is one protocol within the full SDLC and
+ does not claim authorization or execution.
Exit gates:
@@ -190,7 +328,7 @@ Exit gates:
digests and typed failures;
- the installed Python wheel consumes the AOT/native artifact without Gerbil.
-* Phase 5 — Assurance laws in Lean and lifecycle model in TLA+
+* Phase 5 — Assurance laws in Lean and lifecycle models in Quint
Status: =evidence-partial=
@@ -201,14 +339,46 @@ Lean owns general laws, including:
- no stale authority;
- causal modality preservation;
- counterfactual isolation;
-- composition creates a new obligation;
+- [X] composition requires a current, independent direct obligation;
- unknown or conflicted support cannot grant authority.
-TLA+ owns the dynamic lifecycle:
-
-#+begin_example
-change -> invalidate -> plan -> verify -> admit -> decide -> authorize -> effect
-#+end_example
+Quint models the dynamic assurance/execution protocol within the SDLC:
+
+#+begin_export mermaid
+stateDiagram-v2
+ state "Change: admitted" as Changed
+ state "Support: invalidated" as Invalidated
+ state "Obligations: planned" as Planned
+ state "Verification: results produced" as Verified
+ state "Evidence: admitted" as Admitted
+ state "Decision: current" as Decided
+ state "Effect: authorized" as Authorized
+ state "Execution: receipt recorded" as Effect
+ Changed --> Invalidated
+ Invalidated --> Planned
+ Planned --> Verified: actual verifier execution
+ Verified --> Admitted: trusted evidence admission
+ Verified --> Planned: rejected or unresolved
+ Admitted --> Decided: decision obligations discharged
+ Decided --> Authorized: current authorization
+ Authorized --> Effect: Runtime gate and bounded attempt
+ Authorized --> Invalidated: revocation or expiry
+ Effect --> Changed: relevant new observation
+#+end_export
+
+A bounded Quint model in =proof/quint/AssuranceLifecycle.qnt= now checks current
+admission/decision/grant authority, expiry and revocation. Separate mutant
+configurations demonstrate unsafe report-only effect start, stale admission,
+grant before re-verification and effect start after revocation; a reachability
+configuration demonstrates a valid effect path. This is local model evidence,
+not proof that the Runtime Host enforces the contract. Clock/adapter trust,
+counterfactual transition authority, unconditional liveness and source-bound CI replay
+remain open.
+
+A separate stable-environment fairness configuration now checks eventual
+effect start; removing fairness produces a stuttering counterexample. This
+narrows the progress assumption but does not claim progress under source
+changes, clock expiry, revocation or an unavailable Host.
Required model-checking counterexamples include stale cached decisions,
evidence admitted after a source change, release before re-verification,
@@ -224,7 +394,16 @@ Exit gates:
* Phase 6 — POO Flow integration and effect gate
-Status: =planned=
+Status: =active= (the Host Decision preflight can now project a current,
+undecided candidate into POO Flow's POO-native Cedar request family. The
+request is inert and editable; Cedar evaluation, independent Host source
+admission and Runtime effect gating have not landed. The generic Runtime
+boundary rejects its preflight-only marker before Cedar evaluation. The
+Scheme Host can recheck an exact issued request against its current source
+cut, but Runtime does not yet invoke that predicate. POO Flow can now declare
+an action source-admission-required in the administrative Cedar capability;
+request editing cannot remove that requirement. No Aitia admitted =allow=
+Decision or trusted Runtime source-admission path exists yet).
Deliverables:
@@ -234,12 +413,14 @@ Deliverables:
Aitia into the POO Flow core bootstrap.
3. Aitia AOT Scheme exports through a dedicated C ABI using the established
POO Flow Scheme-native result ownership convention.
-4. Aitia's own production Python SDLC Runtime consumes that ABI through CFFI
+4. Aitia's own production Python Runtime consumes that ABI through CFFI
and returns typed
projections. POO Flow's Python runtime remains a separate generic runtime
and lifecycle transport.
-5. Cedar projection from a current admitted Decision, with snapshot and
- invalidation bindings.
+5. [~] Cedar request projection from a current Host preflight, with Decision,
+ claim, policy and snapshot bindings. This is not yet a projection from an
+ admitted =allow= Decision; the Runtime Host must independently validate
+ the source cut at use before any authority can be granted.
6. Runtime Host rejection of stale, unknown, conflicted, revoked or incomplete
assurance closures.
@@ -252,7 +433,46 @@ Exit gates:
* Phase 7 — Software/SDLC reference qualification
-Status: =planned=
+Status: =active= for inert design-contract replacement and snapshot-bound
+implementation-mapping slices; full reference qualification remains open.
+
+The target is the connected lifecycle experiment in RFC 0004: compare design
+alternatives, qualify reusable verification methods, link behavior and design
+evolution trajectories, and reopen obligations after a premise changes.
+Existing bounded results below are foundations for that experiment.
+
+The [[file:docs/receipts/2026-09-24-retirement-impact.org][retirement Impact analysis receipt]] adds seven Scheme qualification cases
+using the existing POO Flow Graph/Temporal Causality mechanisms. The synthetic
+automatic-retry case preserves old dependents and historical cuts, distinguishes
+explicit current activity from past occurrence, and retains unknown consumers
+and future/out-of-horizon work. Candidate graph edits are not migration receipts.
+Real persisted observations, derived migration duties and authorized execution
+remain pending; the lifecycle retirement gate is not closed.
+
+The first job-executor regression now records an ADR-bound POO-native design
+contract with assumptions, guarantees, state ownership, effects and variation
+points. A retry-policy revision reopens the unique-effect guarantee; the
+existing SDLC impact projection uses POO Flow Graph to select its verification
+trajectory. Stronger assumptions, weakened guarantees, new effects, changed
+decision and same-revision mutation have negative tests. This is a pure
+declaration comparison, not a behavioral implication proof, admitted verifier
+receipt or release decision. A follow-on mapping now binds each guarantee to
+exact source artifact and Assurance snapshot revisions/digests. It fails closed
+on source/snapshot changes and uncovered guarantees, but it does not establish
+semantic model-to-code conformance or independent acceptance. A Host support
+projection now requires the guarantee Claim, its declared source dependency,
+an exact source input in a current Host admission, and the Claim's full support
+set. This remains conditional on the configured Host and is not a proof of
+verifier independence or deployment trust.
+
+The Python project's first installed-wheel qualification now runs a real
+worker process twice against a persistent task ledger and loopback provider.
+An intentionally regenerated retry key reproduces two commits after a lost
+acknowledgement; a stable logical-operation key yields one commit and eventual
+task completion. [[file:docs/adrs/0008-stable-operation-key-after-lost-ack.org][ADR 0008]] proposes widening the guarantee from
+"per key" to "per logical operation" and the design-replacement test reopens
+the affected verification. This is known-fault behavioral evidence, not a
+Host-issued seal or proof of a production provider's idempotency.
The first reference case is a software change crossing requirement,
implementation, verification, release-decision and effect boundaries. It
@@ -297,20 +517,74 @@ domain branch and the public API remains POO-native.
* Immediate work queue
-These are the next bounded changes after Phase 0 review:
-
-1. [X] Add the repository contribution policy under the confirmed license contract.
-2. [X] Record the exact SDLC source/test/tool/profile inventory and current receipts.
-3. Move the SDLC and GitOps owners into Aitia and close the published Episteme
- deletion plus parent pin; Aitia-side source and parity are complete.
-4. [X] Add the five-role =modules/assurance/= skeleton with declarations only.
-5. [X] Define the versioned vocabulary manifest and schema identifiers.
-6. [X] Implement relation-plane validation and its negative test matrix.
-7. [X] Implement canonical identity/digest projection.
-8. [X] Define the Aitia Scheme-native C ABI and pure-C harness with an unversioned
- public namespace and explicit ABI revision metadata.
-9. [X] Complete Aitia's =bindings/python= CFFI consumer and wheel receipt.
-10. [X] Create the smallest change-to-invalidation fixture with no verifier calls.
-
-No Python semantic duplicate, Cedar policy, proof migration or effect execution
-belongs in this immediate queue.
+The earlier migration, nominal vocabulary, snapshot/invalidation and CFFI
+foundation work is recorded in the phases above. The current queue is:
+
+Immediate priority (2026-10-10): defer new persistence integration. First
+challenge the design with source-reviewed upstream failure reports and actual
+formal checks. [[file:proof/quint/README.org][Quint design checks]] separate work/cancellation, retirement review, ingress and
+the existing assurance lifecycle; [[file:docs/receipts/2026-09-24-retirement-formal-review.org][the formal review receipt]] records bounded
+exploration and Lean laws, not Runtime closure. Historical
+[[file:docs/receipts/2026-09-24-formal-source-qualification.org][TLA+ source qualification]] is retained as a receipt only. The maintained
+Quint lane checks source syntax through Quint and declared POO Contract/source-cut
+invalidation. The local retirement Host and its execution test now check a
+bounded Scheme behavior cut, including a real callback and negative cases;
+the source binding covers Lean, Quint and Scheme. Independent replay, broader
+semantic correspondence and a refinement argument remain open.
+The [[file:docs/receipts/2026-09-24-retirement-ingress-review.org][bounded ingress composition]] now admits post-retirement requests and
+checks epoch fencing, with two unsafe mutants; deployed authority/Runtime
+correspondence, new grant issuance and eventual progress remain unqualified.
+Extract admission,
+revocation and grant contracts from the existing lifecycle model with atomic
+counterexamples; retain the whole-protocol regression. Do not resume storage
+work merely because these design-level tests pass.
+
+1. [ ] Extend the job-executor design case with a common requirement set and
+ comparable alternatives, keeping completion and unique-effect obligations.
+ Historical-source applicability and the owner/coverage audit are documented
+ in [[file:docs/plans/engineering-case-source-inventory.org][the case-source inventory]] and ADR 0008 r2. Parameter mismatch, retention,
+ independent commit/crash and concurrent-attempt checks remain unqualified.
+ Reuse existing Cedar/Runtime mechanisms for authorization; keep provider
+ effect uniqueness separate. Add an executable source-correction/review case
+ before claiming historical-judgment invalidation is complete.
+2. [ ] Qualify one asset-complete upstream bug case. Record files, exact
+ revisions, licenses, dependencies, resource bounds and required upstream
+ checks in the integration inventory. Do not add a TLA+/TLAPS execution lane.
+3. [ ] Project experimental behavior and design/assurance evolution through
+ POO Flow temporal/causal mechanisms. Check modality, causal cuts, late
+ observations and missing parents without turning structural paths into
+ causal proof.
+ Pair the repair case with one capability-retirement experiment: bind old
+ persisted results, current/in-flight consumers and queued work to pre/post
+ snapshots. Preserve historical identity, distinguish old-but-active state
+ from past occurrence, and derive migration/drain obligations with witnesses.
+ Include an unrelated negative control and unknown external consumers;
+ disappearance from the new graph must not imply safe deletion. Close only
+ with the scoped qualification and effect receipts required by RFC 0004.
+ The [[file:docs/receipts/2026-09-24-retirement-impact.org][first bounded analysis slice]] passes; actual persistent-state and effect
+ qualification remains open and is deferred behind the formal design gates.
+4. [ ] Connect real tool observations from the Aitia Python Runtime to the
+ configured Scheme Host, preserving source bytes and native-session ownership
+ through the AnyIO execution boundary. This is required before claiming
+ admitted support or progressing to the remaining Phase 6 effect gates.
+ The bounded frozen-pytest AnyIO worker now waits for process-group cleanup
+ under cancellation and never carries a thread-affine native session; it
+ remains an observation-only slice, not Host admission. The installed-wheel
+ qualification also exercises a real native session before and after that
+ worker, with the lost-ACK failure/fix and stale-source rejection. The next
+ gate is an owner-preserving Host operation and opaque capability transport,
+ followed by source-change, expiry, revocation and forged-receipt negatives.
+5. [ ] Compose the qualified methods through existing POO Profiles and slots;
+ an override must preserve required checks or reopen corresponding obligations.
+6. [ ] Replay failure, design revision, re-verification and a later assumption
+ change as one lifecycle case. Compare with the same upstream tools/assets
+ alone, and record explanation quality, missed obligations, unnecessary
+ reruns and execution costs.
+7. [ ] Qualify one contract-preserving implementation optimization with both
+ conformance and performance evidence.
+
+Euclid/GCD is a candidate tool qualification, not evidence for an unrelated
+job-executor claim. Known-fault replay is not novel bug discovery. Full Specula
+CLI lifecycle mapping, new checker implementations and MRR integration are not
+required deliverables in this queue. Phase 4/6/7 closure still requires actual
+Host and Runtime receipts rather than a documentation or Recipe-only milestone.
diff --git a/bindings/c/aitia-contract.ss b/bindings/c/aitia-contract.ss
index 50f186a..dabed19 100644
--- a/bindings/c/aitia-contract.ss
+++ b/bindings/c/aitia-contract.ss
@@ -67,7 +67,9 @@
(def (json-check object repository revision)
(github-check
(json-symbol object "name") repository revision
- (json-symbol object "conclusion")))
+ (json-symbol object "conclusion")
+ standard-edition: (hash-get object "standardEdition")
+ source-lock-digest: (hash-get object "sourceLockDigest")))
(def (symbols->json values)
(list->vector (map symbol->string values)))
diff --git a/bindings/c/aitia-native.ss b/bindings/c/aitia-native.ss
index 0062f96..0afb03b 100644
--- a/bindings/c/aitia-native.ss
+++ b/bindings/c/aitia-native.ss
@@ -7,6 +7,9 @@
(import (only-in :std/ffi
C-declare C-ffi-macrology
def-C-lambda def-C-type)
+ ;; Orgize owns the Scheme implementation and public C symbols. Keep
+ ;; this module in Aitia's native link closure without copying its ABI.
+ :orgize/bindings/c/orgize
:poo-flow/lambda-aitia/bindings/c/aitia-contract)
(export aitia-c-round-trip)
diff --git a/bindings/c/aitia-runtime.c b/bindings/c/aitia-runtime.c
index b51f114..0c5d1d4 100644
--- a/bindings/c/aitia-runtime.c
+++ b/bindings/c/aitia-runtime.c
@@ -6,6 +6,7 @@
#include "gambit.h"
#include
+#include
#ifndef POO_FLOW_AITIA_LINKER
#error "POO_FLOW_AITIA_LINKER must name the generated Gambit link-unit linker"
@@ -28,7 +29,11 @@ int32_t poo_flow_aitia_runtime_init(void) {
/* Embedded callers must report uncaught Scheme errors without a REPL. */
setup_params.debug_settings = ___DEBUG_SETTINGS_INITIAL;
status = ___setup(&setup_params);
- if (status != ___FIX(___NO_ERR)) return -1;
+ if (status != ___FIX(___NO_ERR)) {
+ (void)fprintf(stderr, "[lambda-aitia-native] setup-error=%ld\n",
+ (long)___INT(status));
+ return -1;
+ }
poo_flow_aitia_runtime_initialized = 1;
return 0;
}
diff --git a/bindings/python/README.md b/bindings/python/README.md
index 2aff181..b55f600 100644
--- a/bindings/python/README.md
+++ b/bindings/python/README.md
@@ -3,26 +3,119 @@ SPDX-FileCopyrightText: 2026 tao3k team and Contributors
SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
-->
-# Lambda Aitia Python SDLC Runtime
+# Lambda Aitia Python Runtime
This is Lambda Aitia's production Python project, not an example package. It
-is the Python runtime surface for the complete SDLC lifecycle:
+is the Python runtime surface for Aitia across the complete SDLC: requirements,
+design, algorithms and implementation strategies, testing, delivery, operation
+and evolution. The first planning API exposes the assurance/execution protocol
+used within that lifecycle:
-```text
-change -> invalidate -> plan -> verify -> admit -> decide -> authorize -> effect
+```mermaid
+flowchart TD
+ C["change
identified"] --> I["invalidate
dependent support stale"]
+ I --> P["plan
obligations pending"]
+ P --> V["verify
candidate results produced"]
+ V --> A["admit
evidence accepted within scope"]
+ A --> D["decide
decision current"]
+ D --> U["authorize
bounded effect permitted"]
+ U --> E["effect
execution receipt recorded"]
+ V -->|failed or unresolved| P
+ A -->|relevant premise changes| I
+ U -->|revoked or expired| I
```
+These are semantic workflow states, not implementation-progress labels.
+Feedback edges describe lifecycle reassessment, not edges returned by the
+current API. The API projects only an inert, ordered eight-stage DAG, not
+executed transitions. Admission, decision, authorization and execution each
+require their own checked receipt.
+
Lambda Aitia Scheme owns the lifecycle vocabulary, assurance semantics,
decisions and receipts. POO Flow owns the reusable Graph/DAG and runtime
mechanisms. The Python package consumes those contracts through the stable C
ABI and does not reimplement either owner's semantics.
-The current production surface publishes and validates the canonical lifecycle
+AnyIO is a production dependency for the runtime-wide asynchronous execution
+boundary: structured task lifetimes, cancellation and bounded concurrency will
+apply across all Aitia capabilities. The current native session is
+thread-affine, so it must not be passed into `anyio.to_thread.run_sync`; an async
+*native-session* entry point requires a qualified owner-preserving transport
+before it can claim execution.
+
+`lambda_aitia.verification.run_pytest_frozen_async` now runs the bounded pytest
+observer in an AnyIO worker, with an optional capacity limiter. Cancellation
+waits for its timeout-bound process-group cleanup; it never abandons pytest or
+passes an `AitiaNativeSession` to that worker. This returns a source-bound
+observation only, not a Scheme Host admission or authorization.
+
+The current production surface publishes and validates the assurance-flow
DAG through `SdlcRuntime.plan()`. The returned receipt is deliberately inert:
it cannot claim release authorization or runtime execution. Stage execution is
added only as each real Scheme operation and attributable receipt becomes
available; the package will never simulate completion with Python callbacks.
+[RFC 0004](../../docs/rfcs/0004-sdlc-design-and-causal-trajectories.org) defines
+the connected design and trajectory qualification. POO Flow owns generic
+temporal/causal mechanisms; this project integrates actual tool execution and
+scoped observations through its native boundary. Reusable upstream checker,
+model and harness qualification is pending. MRR integration is deferred beyond
+the current Python stage.
+
+`lambda_aitia.verification.run_pytest` is a production subprocess observation
+surface, not a Host-issued admission. It preserves exit status, selected test
+count, JUnit digest and source-tree digest; zero collected tests, skipped-only
+runs, timeouts, missing reports and source drift do not pass its obligation.
+It captures a bounded, canonical source-byte payload and executes pytest only
+from a separately materialized copy; selectors cannot escape that copy.
+`capture_source_tree` and `run_pytest_frozen` expose the same payload boundary
+for a future Host-owned source reader. This still does not prove repository
+provenance, interpreter independence, or trusted external services. The
+Assurance Host owns admission, revocation and execution authority.
+
+`prepare_org_edit_review` is the Aitia-owned preflight for an Org-native
+engineering workspace. It accepts a node ID, byte span and expected old bytes
+from an upstream Orgize projection, checks them against the exact projected
+source digest, rejects stale, moved, duplicate or overlapping edits, and
+returns an inert owner-review proposal. It does not parse Org, apply edits or
+grant authorization. The existing Orgize PR #15 now supplies a Scheme-AOT
+graph-backed source transformation that rechecks node ownership and produces
+candidate text. Aitia-to-Orgize transport and owner-approved persistence are
+still pending; this Python API does not claim those steps.
+
+```python
+from lambda_aitia import OrgNodeEdit, prepare_org_edit_review
+
+review = prepare_org_edit_review(
+ source_path,
+ source_id="design/decision.org",
+ projected_source_digest=orgize_projection.source_digest,
+ edits=(OrgNodeEdit(node_id, start_byte, end_byte, expected_old, replacement),),
+)
+assert review.source_current(source_path) # freshness only; not approval
+```
+
+The POSIX verifier runs pytest in its own process group and stops that group
+on timeout or output overflow. `max_output_bytes` defaults to 8 MiB; only the
+last 8 KiB is retained in the observation. Cleanup uncertainty is reported as
+`control-error`, never as a passing result.
+
+`AitiaNativeSession` keeps one Gambit transport runtime alive across calls to
+`SdlcRuntime(session=...)`, `evaluate_gitops(..., session=...)`, and
+`evaluate_org_contract(..., session=...)`. It is
+process-exclusive and thread-affine; use it as a context manager. It does not
+issue Host verification seals or admission capabilities. A separate Scheme
+Host operation and opaque ABI handles are still required before Python
+observations can be admitted.
+
+`evaluate_org_contract` calls Orgize's versioned C ABI from the same initialized
+native link unit. The input is a sequence of `OrgElementFact` values already
+projected by the source owner, not raw Org text. Orgize owns Element graph
+queries and Contract evaluation; Aitia does not parse Org or treat a matching
+contract as authenticated provenance, review, admission, or effect authority.
+The binary row/result layout and revision are defined in Orgize's
+`bindings/c/include/orgize.h`. Aitia checks the revision before each call.
+
```python
from lambda_aitia import SdlcRuntime
@@ -36,22 +129,35 @@ Builds require `LAMBDA_AITIA_NATIVE_LIBRARY` to identify the qualified native
library that will be bundled into the wheel.
The binding is an independent uv project owned entirely by Lambda Aitia. From
-this directory, synchronize and test the locked environment with:
+the repository root, refresh the generated CFFI extension and check its current
+Orgize row type with:
```sh
-env -u SDKROOT uv sync --locked --extra test
-env -u SDKROOT uv run --locked --extra test pytest
+just dev-python
```
-Build the distributable wheel after producing the Scheme-native library:
+To remove only stale generated CFFI modules and their temporary C objects:
```sh
-LAMBDA_AITIA_NATIVE_LIBRARY=/absolute/path/to/libpoo_flow_aitia.dylib \
- env -u SDKROOT uv sync --locked --extra test
-LAMBDA_AITIA_NATIVE_LIBRARY=/absolute/path/to/libpoo_flow_aitia.dylib \
- env -u SDKROOT uv build --wheel --offline --no-build-isolation
+just clean-python-cffi
```
+`dev-python` requires the declared POO Flow/Gerbil dependencies, including
+Orgize's C header, in the selected `GERBIL_PATH`. It cleans and rebuilds only
+the in-place CFFI extension, without compiling POO Flow or replacing the native
+library. Run `just check-native` against the qualified POO Flow version for
+the complete Scheme/native/Python and wheel qualification.
+The `justfile` removes `SDKROOT` only on Darwin; it does not alter that
+environment variable on Linux.
+
+`just check-native` also installs that wheel in a fresh uv environment outside
+the repository, runs the native plan, reproduces a lost-acknowledgement bug in
+two real worker processes and a loopback HTTP service, checks the corrected
+worker through the AnyIO frozen-source verifier while the main thread retains
+one native session, then rejects the old observation after a source edit. This
+is a known fault-injection qualification case, not a newly discovered
+production defect or an Assurance Host seal.
+
On Darwin, the repository `justfile` selects `/usr/bin/cc`, unsets `SDKROOT`
for Gerbil invocations, and keeps the deployment metadata consistent with the
installed Gerbil/Gambit native objects. The large Gambit closure uses DWARF
diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml
index 897797b..e44a773 100644
--- a/bindings/python/pyproject.toml
+++ b/bindings/python/pyproject.toml
@@ -9,12 +9,12 @@ build-backend = "setuptools.build_meta"
[project]
name = "lambda-aitia"
version = "0.1.0"
-description = "Production Python runtime for Lambda Aitia's Scheme-owned SDLC"
+description = "Production Python runtime for Lambda Aitia"
readme = "README.md"
requires-python = ">=3.11"
license = "Apache-2.0 AND LGPL-2.1-or-later"
license-files = ["LICENSE"]
-dependencies = ["cffi>=2.0"]
+dependencies = ["anyio>=4.14.1", "cffi>=2.0"]
[project.optional-dependencies]
test = ["pytest>=8.0", "setuptools>=77"]
diff --git a/bindings/python/qualification/job_executor/README.org b/bindings/python/qualification/job_executor/README.org
new file mode 100644
index 0000000..235cb97
--- /dev/null
+++ b/bindings/python/qualification/job_executor/README.org
@@ -0,0 +1,59 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Python job-executor qualification case
+
+This case belongs to the production =bindings/python= uv project. Its worker
+is a real child process, its task ledger and provider effect log are SQLite
+files, and the effect provider is a loopback HTTP server. The service commits
+once, withholds the first acknowledgement, and lets a restarted worker retry.
+
+The =buggy= strategy generates a new provider idempotency key after restart:
+the test must fail with two commits. The =fixed= strategy retains a key derived
+from the logical operation: the same test must pass with one commit and a
+completed task. This is a deliberate known-fault replay, not a novel bug.
+
+The installed-wheel project test copies the scenario outside the checkout,
+installs the built wheel and locked dependencies in a new uv environment, and
+runs both strategies through =lambda_aitia.verification.run_pytest=. It also
+changes one source file after the passing observation and requires that
+=source_current= reject the old observation. The observation is not a Scheme
+Host admission. Pytest runs from captured source bytes in a fresh directory,
+but repository provenance, Scheme Host admission, independent provider trust,
+and a Runtime effect gate remain open.
+
+The case challenges the original guarantee's *per key* scope: a provider can
+honor that scope while a logical operation is duplicated with two keys.
+[[file:../../../../docs/adrs/0008-stable-operation-key-after-lost-ack.org][ADR 0008]]
+proposes a revised logical-operation guarantee and keeps its acceptance and
+re-verification separate from this test result.
+
+* Coverage boundary
+
+The worker has one fixed job and constant payload; the provider's single SQLite
+row represents both the deduplication key and the effect. This is not a model
+of multi-tenant identity, changed intent, retention expiry, concurrent attempts
+or atomicity across a ledger and an independent external service. Fixed mode
+repairs the injected identity fault, not every idempotency failure mode.
+
+ADR 0008's r2 coverage matrix assigns those remaining qualifications to their
+owners. Existing POO Flow Cedar authorization and Runtime effect mechanisms
+must be reused, not reimplemented in this fixture. A Cedar permit answers an
+authorization question; it does not establish provider commit uniqueness or
+resolve an unknown outcome after a lost acknowledgement.
+
+* Focused replay receipt — 2026-09-24
+
+The unchanged worker/test sources at Aitia commit
+=f3438241780e863db6c0b518d86628b540f96cd1= were rerun during the ADR r2 audit.
+From =bindings/python=, =uv run --locked --no-sync= ran
+=pytest qualification/job_executor/test_unique_effect.py= with a process-scoped
+=AITIA_QUALIFICATION_MODE=:
+
+- =fixed=: one test passed; one commit and task completion were asserted.
+- =buggy=: the expected negative test failed specifically at =committed == 1=,
+ observing two commits, rather than failing to start the worker/provider.
+
+This was a focused source-fixture replay in the existing uv environment, not a
+new installed-wheel qualification or Cedar/Runtime execution receipt. The new
+owner matrix does not promote untested boundaries to passing evidence.
diff --git a/bindings/python/qualification/job_executor/test_unique_effect.py b/bindings/python/qualification/job_executor/test_unique_effect.py
new file mode 100644
index 0000000..77fedc6
--- /dev/null
+++ b/bindings/python/qualification/job_executor/test_unique_effect.py
@@ -0,0 +1,72 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+#
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""Project qualification of acknowledgement loss in real worker processes."""
+
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+import os
+from pathlib import Path
+import sqlite3
+import subprocess
+import sys
+import tempfile
+from threading import Thread
+
+
+def test_unique_effect_after_lost_ack_and_restart() -> None:
+ mode = os.environ["AITIA_QUALIFICATION_MODE"]
+ with tempfile.TemporaryDirectory(prefix="aitia-worker-state-") as directory:
+ state = Path(directory)
+ effects = state / "effects.sqlite"
+ ledger = state / "tasks.sqlite"
+
+ class EffectHandler(BaseHTTPRequestHandler):
+ def do_POST(self) -> None:
+ key = self.headers.get("Idempotency-Key")
+ assert key
+ with sqlite3.connect(effects) as database:
+ database.execute(
+ "create table if not exists effects "
+ "(key text primary key, payload blob not null)"
+ )
+ previous = database.execute(
+ "select count(*) from effects"
+ ).fetchone()[0]
+ database.execute(
+ "insert or ignore into effects values (?, ?)",
+ (key, self.rfile.read(int(self.headers["Content-Length"]))),
+ )
+ if previous == 0:
+ # The service committed; the worker never receives its ACK.
+ self.close_connection = True
+ return
+ self.send_response(200)
+ self.end_headers()
+
+ def log_message(self, format: str, *args: object) -> None:
+ pass
+
+ server = ThreadingHTTPServer(("127.0.0.1", 0), EffectHandler)
+ thread = Thread(target=server.serve_forever, daemon=True)
+ thread.start()
+ try:
+ endpoint = f"http://127.0.0.1:{server.server_port}/effect"
+ command = (sys.executable, str(Path(__file__).with_name("worker.py")),
+ str(ledger), endpoint, mode)
+ first = subprocess.run(command, check=False, timeout=10)
+ second = subprocess.run(command, check=False, timeout=10)
+ assert first.returncode == 2
+ assert second.returncode == 0
+ with sqlite3.connect(effects) as database:
+ committed = database.execute("select count(*) from effects").fetchone()[0]
+ with sqlite3.connect(ledger) as database:
+ final_state = database.execute(
+ "select state from tasks where job = 'job-42'"
+ ).fetchone()[0]
+ assert committed == 1
+ assert final_state == "complete"
+ finally:
+ server.shutdown()
+ server.server_close()
+ thread.join(timeout=5)
diff --git a/bindings/python/qualification/job_executor/worker.py b/bindings/python/qualification/job_executor/worker.py
new file mode 100644
index 0000000..dc8ab9d
--- /dev/null
+++ b/bindings/python/qualification/job_executor/worker.py
@@ -0,0 +1,41 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+#
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""Qualification worker with a persistent task ledger and HTTP side effect."""
+
+import sqlite3
+import sys
+from urllib.request import ProxyHandler, Request, build_opener
+from uuid import uuid4
+
+
+def execute(ledger_path: str, endpoint: str, mode: str) -> int:
+ if mode not in ("buggy", "fixed"):
+ raise ValueError("unknown worker mode")
+ with sqlite3.connect(ledger_path) as ledger:
+ ledger.execute(
+ "create table if not exists tasks (job text primary key, state text not null)"
+ )
+ ledger.execute("insert or ignore into tasks values ('job-42', 'pending')")
+ state = ledger.execute(
+ "select state from tasks where job = 'job-42'"
+ ).fetchone()[0]
+ if state == "complete":
+ return 0
+ # Bug: regenerating the provider key after restart creates a second effect.
+ key = "job-42" if mode == "fixed" else uuid4().hex
+ request = Request(endpoint, data=b"commit", headers={"Idempotency-Key": key})
+ try:
+ with build_opener(ProxyHandler({})).open(request, timeout=3) as response:
+ if response.status != 200:
+ return 2
+ except Exception:
+ return 2
+ with sqlite3.connect(ledger_path) as ledger:
+ ledger.execute("update tasks set state = 'complete' where job = 'job-42'")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(execute(sys.argv[1], sys.argv[2], sys.argv[3]))
diff --git a/bindings/python/src/lambda_aitia/__init__.py b/bindings/python/src/lambda_aitia/__init__.py
index 93da0ed..05c02b0 100644
--- a/bindings/python/src/lambda_aitia/__init__.py
+++ b/bindings/python/src/lambda_aitia/__init__.py
@@ -2,17 +2,26 @@
#
# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
-"""Production Python runtime for Lambda Aitia's Scheme-owned SDLC."""
+"""Production Python runtime for Lambda Aitia."""
from .gitops import GitOpsDecision, evaluate_gitops
-from .native import AitiaNativeError, descriptor
+from .native import AitiaNativeError, AitiaNativeSession, descriptor
+from .org_contract import OrgContractResult, OrgElementFact, evaluate_org_contract
+from .org_workspace import OrgEditReview, OrgNodeEdit, prepare_org_edit_review
from .runtime import SdlcFlowPlan, SdlcRuntime
__all__ = [
"AitiaNativeError",
+ "AitiaNativeSession",
"GitOpsDecision",
+ "OrgContractResult",
+ "OrgElementFact",
+ "OrgEditReview",
+ "OrgNodeEdit",
"SdlcFlowPlan",
"SdlcRuntime",
"descriptor",
"evaluate_gitops",
+ "evaluate_org_contract",
+ "prepare_org_edit_review",
]
diff --git a/bindings/python/src/lambda_aitia/_native/_build.py b/bindings/python/src/lambda_aitia/_native/_build.py
index 96aed4a..d150683 100644
--- a/bindings/python/src/lambda_aitia/_native/_build.py
+++ b/bindings/python/src/lambda_aitia/_native/_build.py
@@ -5,12 +5,26 @@
from __future__ import annotations
from pathlib import Path
+import os
from cffi import FFI
ffibuilder = FFI()
ffibuilder.cdef(
"""
+ typedef struct lambda_aitia_python_session lambda_aitia_python_session;
+ lambda_aitia_python_session *lambda_aitia_python_open(
+ const char *library_path, char *error, size_t error_capacity);
+ void lambda_aitia_python_close(lambda_aitia_python_session *session);
+ int lambda_aitia_python_session_call(
+ lambda_aitia_python_session *session,
+ const char *operation,
+ const uint8_t *payload,
+ size_t payload_length,
+ uint8_t **output,
+ size_t *output_length,
+ char *error,
+ size_t error_capacity);
int lambda_aitia_python_call(
const char *library_path,
const char *operation,
@@ -21,19 +35,47 @@
char *error,
size_t error_capacity);
void lambda_aitia_python_release(uint8_t *output);
+ typedef struct {
+ int64_t id;
+ int64_t parent_id;
+ const char *kind;
+ const char *field_name;
+ const char *field_value;
+ } orgize_element_row;
+ typedef struct {
+ int32_t status;
+ uint32_t matched_count;
+ int32_t passed;
+ } orgize_contract_result;
+ int lambda_aitia_python_session_org_contract(
+ lambda_aitia_python_session *session, const orgize_element_row *rows,
+ uint32_t row_count, int64_t scope_id, const char *query_kind,
+ const char *field_name, const char *field_value,
+ uint32_t expectation, uint32_t expected_count,
+ orgize_contract_result *result, char *error, size_t error_capacity);
"""
)
native_dir = Path(__file__).resolve().parent
c_binding_dir = native_dir.parents[3] / "c"
+gerbil_path = os.environ.get("GERBIL_PATH")
+if gerbil_path is None:
+ raise RuntimeError("GERBIL_PATH must identify the installed Orgize dependency")
+orgize_c_dir = (
+ Path(gerbil_path).resolve()
+ / "pkg/github.com/tao3k/orgize/bindings/c/include"
+)
+if not (orgize_c_dir / "orgize.h").is_file():
+ raise RuntimeError("Orgize's public C header is absent; install POO Flow dependencies")
ffibuilder.set_source(
"lambda_aitia._native._aitia_cffi",
'#include "aitia_shim.c"',
- include_dirs=[str(native_dir), str(c_binding_dir / "include")],
+ include_dirs=[str(native_dir), str(c_binding_dir / "include"), str(orgize_c_dir)],
)
if __name__ == "__main__":
import sys
+ import sysconfig
python_project = Path(__file__).resolve().parents[3]
if str(python_project) not in sys.path:
@@ -43,6 +85,8 @@
sanitize_python_linker_config()
ffibuilder.compile(
tmpdir=str(native_dir / "_build_temp"),
- target=str(native_dir / "_aitia_cffi.*"),
+ target=str(
+ native_dir / ("_aitia_cffi" + sysconfig.get_config_var("EXT_SUFFIX"))
+ ),
verbose=True,
)
diff --git a/bindings/python/src/lambda_aitia/_native/aitia_shim.c b/bindings/python/src/lambda_aitia/_native/aitia_shim.c
index e0b80b3..c517c3b 100644
--- a/bindings/python/src/lambda_aitia/_native/aitia_shim.c
+++ b/bindings/python/src/lambda_aitia/_native/aitia_shim.c
@@ -3,7 +3,9 @@
// SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
#include "lambda_aitia/aitia.h"
+#include "orgize.h"
+#include
#include
#include
#include
@@ -31,6 +33,24 @@ typedef void (*lambda_aitia_result_init_fn)(poo_flow_aitia_result *);
typedef void (*lambda_aitia_result_release_fn)(poo_flow_aitia_result *);
typedef int32_t (*lambda_aitia_descriptor_fn)(poo_flow_aitia_result *);
typedef int32_t (*lambda_aitia_evaluate_fn)(char *, poo_flow_aitia_result *);
+typedef uint32_t (*orgize_revision_fn)(void);
+typedef int32_t (*orgize_evaluate_fn)(const orgize_element_row *, uint32_t,
+ int64_t, const char *, const char *,
+ const char *, uint32_t, uint32_t,
+ orgize_contract_result *);
+
+typedef struct lambda_aitia_python_session {
+ lambda_aitia_library library;
+ lambda_aitia_runtime_shutdown_fn runtime_shutdown;
+ lambda_aitia_result_init_fn result_init;
+ lambda_aitia_result_release_fn result_release;
+ lambda_aitia_descriptor_fn descriptor;
+ lambda_aitia_evaluate_fn sdlc_flow_plan;
+ lambda_aitia_evaluate_fn evaluate;
+} lambda_aitia_python_session;
+
+/* Gambit has one process runtime. A live session excludes transient calls. */
+static atomic_flag lambda_aitia_runtime_lease = ATOMIC_FLAG_INIT;
static int lambda_aitia_load_symbol(lambda_aitia_library library,
const char *name, void *destination,
@@ -57,121 +77,187 @@ static const char *lambda_aitia_loader_error(void) {
#endif
}
-void lambda_aitia_python_release(uint8_t *output) { free(output); }
-
-int lambda_aitia_python_call(const char *library_path, const char *operation,
- const uint8_t *payload, size_t payload_length,
- uint8_t **output, size_t *output_length,
- char *error, size_t error_capacity) {
- lambda_aitia_library library;
+lambda_aitia_python_session *lambda_aitia_python_open(
+ const char *library_path, char *error, size_t error_capacity) {
+ lambda_aitia_python_session *session;
lambda_aitia_revision_fn revision;
lambda_aitia_runtime_init_fn runtime_init;
- lambda_aitia_runtime_shutdown_fn runtime_shutdown;
- lambda_aitia_result_init_fn result_init;
- lambda_aitia_result_release_fn result_release;
- lambda_aitia_descriptor_fn descriptor;
- lambda_aitia_evaluate_fn sdlc_flow_plan;
- lambda_aitia_evaluate_fn evaluate;
- poo_flow_aitia_result result;
- char *input = NULL;
- int32_t status;
- if (output == NULL || output_length == NULL || library_path == NULL ||
- operation == NULL) {
- lambda_aitia_write_error(error, error_capacity, "invalid Aitia call");
- return -100;
+ if (library_path == NULL || library_path[0] == '\0') {
+ lambda_aitia_write_error(error, error_capacity, "invalid Aitia library path");
+ return NULL;
}
- *output = NULL;
- *output_length = 0;
- library = LAMBDA_AITIA_OPEN(library_path);
- if (library == NULL) {
+ if (atomic_flag_test_and_set(&lambda_aitia_runtime_lease)) {
+ lambda_aitia_write_error(error, error_capacity,
+ "Lambda Aitia runtime already has an active session");
+ return NULL;
+ }
+ session = (lambda_aitia_python_session *)calloc(1, sizeof(*session));
+ if (session == NULL) {
+ lambda_aitia_write_error(error, error_capacity, "session allocation failed");
+ goto fail_lease;
+ }
+ session->library = LAMBDA_AITIA_OPEN(library_path);
+ if (session->library == NULL) {
lambda_aitia_write_error(error, error_capacity, lambda_aitia_loader_error());
- return -101;
+ goto fail_session;
}
- if (!lambda_aitia_load_symbol(library, "poo_flow_aitia_abi_revision",
+ if (!lambda_aitia_load_symbol(session->library, "poo_flow_aitia_abi_revision",
&revision, sizeof(revision)) ||
- !lambda_aitia_load_symbol(library, "poo_flow_aitia_runtime_init",
+ !lambda_aitia_load_symbol(session->library, "poo_flow_aitia_runtime_init",
&runtime_init, sizeof(runtime_init)) ||
- !lambda_aitia_load_symbol(library, "poo_flow_aitia_runtime_shutdown",
- &runtime_shutdown,
- sizeof(runtime_shutdown)) ||
- !lambda_aitia_load_symbol(library, "poo_flow_aitia_result_init",
- &result_init, sizeof(result_init)) ||
- !lambda_aitia_load_symbol(library, "poo_flow_aitia_result_release",
- &result_release, sizeof(result_release)) ||
- !lambda_aitia_load_symbol(library, "poo_flow_aitia_descriptor",
- &descriptor, sizeof(descriptor)) ||
- !lambda_aitia_load_symbol(library, "poo_flow_aitia_sdlc_flow_plan",
- &sdlc_flow_plan, sizeof(sdlc_flow_plan)) ||
- !lambda_aitia_load_symbol(library, "poo_flow_aitia_gitops_evaluate",
- &evaluate, sizeof(evaluate))) {
+ !lambda_aitia_load_symbol(session->library, "poo_flow_aitia_runtime_shutdown",
+ &session->runtime_shutdown,
+ sizeof(session->runtime_shutdown)) ||
+ !lambda_aitia_load_symbol(session->library, "poo_flow_aitia_result_init",
+ &session->result_init, sizeof(session->result_init)) ||
+ !lambda_aitia_load_symbol(session->library, "poo_flow_aitia_result_release",
+ &session->result_release,
+ sizeof(session->result_release)) ||
+ !lambda_aitia_load_symbol(session->library, "poo_flow_aitia_descriptor",
+ &session->descriptor, sizeof(session->descriptor)) ||
+ !lambda_aitia_load_symbol(session->library, "poo_flow_aitia_sdlc_flow_plan",
+ &session->sdlc_flow_plan,
+ sizeof(session->sdlc_flow_plan)) ||
+ !lambda_aitia_load_symbol(session->library, "poo_flow_aitia_gitops_evaluate",
+ &session->evaluate, sizeof(session->evaluate))) {
lambda_aitia_write_error(error, error_capacity,
"Lambda Aitia ABI symbol is absent");
- LAMBDA_AITIA_CLOSE(library);
- return -102;
+ goto fail_library;
}
if (runtime_init() != 0) {
lambda_aitia_write_error(error, error_capacity,
"Lambda Aitia runtime initialization failed");
- LAMBDA_AITIA_CLOSE(library);
- return -103;
+ goto fail_library;
}
if (revision() != POO_FLOW_AITIA_ABI_REVISION) {
lambda_aitia_write_error(error, error_capacity,
"Lambda Aitia ABI revision mismatch");
- runtime_shutdown();
- LAMBDA_AITIA_CLOSE(library);
- return -104;
+ session->runtime_shutdown();
+ goto fail_library;
+ }
+ return session;
+
+fail_library:
+ LAMBDA_AITIA_CLOSE(session->library);
+fail_session:
+ free(session);
+fail_lease:
+ atomic_flag_clear(&lambda_aitia_runtime_lease);
+ return NULL;
+}
+
+void lambda_aitia_python_close(lambda_aitia_python_session *session) {
+ if (session == NULL) return;
+ session->runtime_shutdown();
+ LAMBDA_AITIA_CLOSE(session->library);
+ free(session);
+ atomic_flag_clear(&lambda_aitia_runtime_lease);
+}
+
+void lambda_aitia_python_release(uint8_t *output) { free(output); }
+
+int lambda_aitia_python_session_call(
+ lambda_aitia_python_session *session, const char *operation,
+ const uint8_t *payload, size_t payload_length, uint8_t **output,
+ size_t *output_length, char *error, size_t error_capacity) {
+ poo_flow_aitia_result result;
+ char *input = NULL;
+ int32_t status;
+
+ if (session == NULL || output == NULL || output_length == NULL ||
+ operation == NULL) {
+ lambda_aitia_write_error(error, error_capacity, "invalid Aitia call");
+ return -100;
}
- result_init(&result);
+ *output = NULL;
+ *output_length = 0;
+ session->result_init(&result);
if (strcmp(operation, "descriptor") == 0) {
- status = descriptor(&result);
+ status = session->descriptor(&result);
} else if (strcmp(operation, "sdlc-flow-plan") == 0 ||
strcmp(operation, "gitops-evaluate") == 0) {
if (payload == NULL || payload_length == 0 || payload_length == SIZE_MAX) {
lambda_aitia_write_error(error, error_capacity,
"Aitia operation requires JSON input");
- result_release(&result);
- runtime_shutdown();
- LAMBDA_AITIA_CLOSE(library);
- return -105;
+ status = -105;
+ goto done;
}
input = (char *)malloc(payload_length + 1u);
if (input == NULL) {
lambda_aitia_write_error(error, error_capacity, "input allocation failed");
- result_release(&result);
- runtime_shutdown();
- LAMBDA_AITIA_CLOSE(library);
- return -106;
+ status = -106;
+ goto done;
}
memcpy(input, payload, payload_length);
input[payload_length] = '\0';
status = strcmp(operation, "sdlc-flow-plan") == 0
- ? sdlc_flow_plan(input, &result)
- : evaluate(input, &result);
- free(input);
+ ? session->sdlc_flow_plan(input, &result)
+ : session->evaluate(input, &result);
} else {
lambda_aitia_write_error(error, error_capacity, "unknown Aitia operation");
- result_release(&result);
- runtime_shutdown();
- LAMBDA_AITIA_CLOSE(library);
- return -107;
+ status = -107;
+ goto done;
}
if (result.payload != NULL && result.length > 0) {
*output = (uint8_t *)malloc(result.length);
if (*output == NULL) {
- result_release(&result);
lambda_aitia_write_error(error, error_capacity,
"output allocation failed");
- runtime_shutdown();
- LAMBDA_AITIA_CLOSE(library);
- return -108;
+ status = -108;
+ goto done;
}
memcpy(*output, result.payload, result.length);
*output_length = result.length;
}
- result_release(&result);
- runtime_shutdown();
- LAMBDA_AITIA_CLOSE(library);
+
+done:
+ free(input);
+ session->result_release(&result);
+ return status;
+}
+
+int lambda_aitia_python_session_org_contract(
+ lambda_aitia_python_session *session, const orgize_element_row *rows,
+ uint32_t row_count, int64_t scope_id, const char *query_kind,
+ const char *field_name, const char *field_value, uint32_t expectation,
+ uint32_t expected_count, orgize_contract_result *result,
+ char *error, size_t error_capacity) {
+ orgize_revision_fn revision;
+ orgize_evaluate_fn evaluate;
+
+ if (session == NULL || result == NULL) {
+ lambda_aitia_write_error(error, error_capacity, "invalid Orgize call");
+ return -100;
+ }
+ if (!lambda_aitia_load_symbol(session->library, "orgize_abi_revision",
+ &revision, sizeof(revision)) ||
+ !lambda_aitia_load_symbol(session->library, "orgize_contract_evaluate",
+ &evaluate, sizeof(evaluate))) {
+ lambda_aitia_write_error(error, error_capacity,
+ "Orgize ABI symbols are absent");
+ return -101;
+ }
+ if (revision() != 1u) {
+ lambda_aitia_write_error(error, error_capacity,
+ "Orgize ABI revision mismatch");
+ return -102;
+ }
+ return evaluate(rows, row_count, scope_id, query_kind, field_name,
+ field_value, expectation, expected_count, result);
+}
+
+int lambda_aitia_python_call(const char *library_path, const char *operation,
+ const uint8_t *payload, size_t payload_length,
+ uint8_t **output, size_t *output_length,
+ char *error, size_t error_capacity) {
+ lambda_aitia_python_session *session =
+ lambda_aitia_python_open(library_path, error, error_capacity);
+ int status;
+ if (session == NULL) return -101;
+ status = lambda_aitia_python_session_call(
+ session, operation, payload, payload_length, output, output_length,
+ error, error_capacity);
+ lambda_aitia_python_close(session);
return status;
}
diff --git a/bindings/python/src/lambda_aitia/gitops.py b/bindings/python/src/lambda_aitia/gitops.py
index 6114711..cc66230 100644
--- a/bindings/python/src/lambda_aitia/gitops.py
+++ b/bindings/python/src/lambda_aitia/gitops.py
@@ -9,7 +9,7 @@
from dataclasses import dataclass
from typing import Any, Mapping
-from .native import AitiaNativeError, _evaluate_gitops_payload
+from .native import AitiaNativeError, AitiaNativeSession, _evaluate_gitops_payload
from .runtime import _boolean, _string, _strings
@@ -67,9 +67,13 @@ def from_payload(cls, payload: Mapping[str, Any]) -> GitOpsDecision:
return decision
-def evaluate_gitops(change: Mapping[str, Any]) -> GitOpsDecision:
+def evaluate_gitops(
+ change: Mapping[str, Any], *, session: AitiaNativeSession | None = None
+) -> GitOpsDecision:
"""Evaluate check evidence without implying release authorization or execution."""
if not isinstance(change, Mapping):
raise TypeError("change must be a mapping")
- return GitOpsDecision.from_payload(_evaluate_gitops_payload(change))
+ return GitOpsDecision.from_payload(
+ _evaluate_gitops_payload(change, session=session)
+ )
diff --git a/bindings/python/src/lambda_aitia/native.py b/bindings/python/src/lambda_aitia/native.py
index 5ba9b88..d41b36e 100644
--- a/bindings/python/src/lambda_aitia/native.py
+++ b/bindings/python/src/lambda_aitia/native.py
@@ -10,6 +10,8 @@
import os
from pathlib import Path
import platform
+from threading import get_ident
+from types import TracebackType
from typing import Any, Mapping
from ._native._aitia_cffi import ffi, lib
@@ -36,21 +38,9 @@ def _library_path() -> Path:
return candidate
-def _call(operation: str, payload: bytes = b"") -> dict[str, Any]:
- output = ffi.new("uint8_t **")
- output_length = ffi.new("size_t *")
- error = ffi.new("char[512]")
- payload_pointer = ffi.from_buffer(payload) if payload else ffi.NULL
- status = lib.lambda_aitia_python_call(
- os.fsencode(_library_path()),
- operation.encode("ascii"),
- payload_pointer,
- len(payload),
- output,
- output_length,
- error,
- 512,
- )
+def _decode_call(
+ status: int, output: Any, output_length: Any, error: Any
+) -> dict[str, Any]:
try:
response_bytes = (
bytes(ffi.buffer(output[0], output_length[0]))
@@ -73,23 +63,119 @@ def _call(operation: str, payload: bytes = b"") -> dict[str, Any]:
return value
+def _call(operation: str, payload: bytes = b"") -> dict[str, Any]:
+ output = ffi.new("uint8_t **")
+ output_length = ffi.new("size_t *")
+ error = ffi.new("char[512]")
+ payload_pointer = ffi.from_buffer(payload) if payload else ffi.NULL
+ status = lib.lambda_aitia_python_call(
+ os.fsencode(_library_path()),
+ operation.encode("ascii"),
+ payload_pointer,
+ len(payload),
+ output,
+ output_length,
+ error,
+ 512,
+ )
+ return _decode_call(status, output, output_length, error)
+
+
+class AitiaNativeSession:
+ """Own one process-exclusive Gambit transport session, not Host authority.
+
+ Calls and close must run on the thread that opened the session. Its lifetime
+ keeps Scheme state alive, but only Scheme-issued opaque capabilities can
+ establish verification/admission authority in a later ABI revision.
+ """
+
+ def __init__(self) -> None:
+ error = ffi.new("char[512]")
+ handle = lib.lambda_aitia_python_open(
+ os.fsencode(_library_path()), error, 512
+ )
+ if handle == ffi.NULL:
+ raise AitiaNativeError(ffi.string(error).decode("utf-8", errors="replace"))
+ self._handle = handle
+ self._owner_thread = get_ident()
+
+ @property
+ def closed(self) -> bool:
+ return self._handle == ffi.NULL
+
+ def _require_owner(self) -> None:
+ if get_ident() != self._owner_thread:
+ raise AitiaNativeError("Aitia native session belongs to another thread")
+
+ def close(self) -> None:
+ self._require_owner()
+ if not self.closed:
+ handle = self._handle
+ self._handle = ffi.NULL
+ lib.lambda_aitia_python_close(handle)
+
+ def _call(self, operation: str, payload: bytes = b"") -> dict[str, Any]:
+ self._require_owner()
+ if self.closed:
+ raise AitiaNativeError("Aitia native session is closed")
+ output = ffi.new("uint8_t **")
+ output_length = ffi.new("size_t *")
+ error = ffi.new("char[512]")
+ payload_pointer = ffi.from_buffer(payload) if payload else ffi.NULL
+ status = lib.lambda_aitia_python_session_call(
+ self._handle,
+ operation.encode("ascii"),
+ payload_pointer,
+ len(payload),
+ output,
+ output_length,
+ error,
+ 512,
+ )
+ return _decode_call(status, output, output_length, error)
+
+ def __enter__(self) -> AitiaNativeSession:
+ self._require_owner()
+ if self.closed:
+ raise AitiaNativeError("Aitia native session is closed")
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc: BaseException | None,
+ traceback: TracebackType | None,
+ ) -> None:
+ self.close()
+
+ def __del__(self) -> None:
+ if getattr(self, "_handle", ffi.NULL) != ffi.NULL:
+ # Gambit cleanup is thread-affine. Refuse silent cross-thread cleanup.
+ if get_ident() == self._owner_thread:
+ self.close()
+
+
def descriptor() -> dict[str, Any]:
"""Return the native ABI descriptor published by Scheme."""
return _call("descriptor")
-def sdlc_flow_plan(lifecycle_id: str) -> dict[str, Any]:
+def sdlc_flow_plan(
+ lifecycle_id: str, *, session: AitiaNativeSession | None = None
+) -> dict[str, Any]:
"""Ask Scheme to project an SDLC lifecycle onto the POO Flow DAG."""
payload = json.dumps(
{"lifecycle-id": lifecycle_id}, separators=(",", ":"), ensure_ascii=False
).encode()
- return _call("sdlc-flow-plan", payload)
+ return (session._call if session is not None else _call)("sdlc-flow-plan", payload)
-def _evaluate_gitops_payload(change: Mapping[str, Any]) -> dict[str, Any]:
+def _evaluate_gitops_payload(
+ change: Mapping[str, Any], *, session: AitiaNativeSession | None = None
+) -> dict[str, Any]:
"""Ask Scheme to evaluate one GitOps change and its check evidence."""
payload = json.dumps(change, separators=(",", ":"), ensure_ascii=False).encode()
- return _call("gitops-evaluate", payload)
+ return (session._call if session is not None else _call)("gitops-evaluate", payload)
diff --git a/bindings/python/src/lambda_aitia/org_contract.py b/bindings/python/src/lambda_aitia/org_contract.py
new file mode 100644
index 0000000..bd9227c
--- /dev/null
+++ b/bindings/python/src/lambda_aitia/org_contract.py
@@ -0,0 +1,145 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+#
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""Call Orgize's Scheme-owned Element/Contract C ABI from Aitia's runtime.
+
+Rows are already-admitted Org graph facts. Org parsing and contract semantics
+remain in Orgize; this module only owns Python transport and lifetime checks.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import Literal, Sequence
+
+from ._native._aitia_cffi import ffi, lib
+from .native import AitiaNativeError, AitiaNativeSession
+
+
+Expectation = Literal["exactly", "at-least", "at-most"]
+_EXPECTATION_CODE = {"exactly": 0, "at-least": 1, "at-most": 2}
+_INT64_MAX = (1 << 63) - 1
+
+
+@dataclass(frozen=True, slots=True)
+class OrgElementFact:
+ """One Orgize-projected graph node with at most one projected field."""
+
+ id: int
+ parent_id: int
+ kind: str
+ field_name: str = ""
+ field_value: str = ""
+
+
+@dataclass(frozen=True, slots=True)
+class OrgContractResult:
+ matched_count: int
+ passed: bool
+
+
+def _integer(value: int, *, name: str, minimum: int, maximum: int) -> int:
+ if type(value) is not int or not minimum <= value <= maximum:
+ raise ValueError(f"{name} must be an integer between {minimum} and {maximum}")
+ return value
+
+
+def _utf8(value: str, *, name: str, required: bool = False) -> bytes:
+ if not isinstance(value, str) or (required and not value):
+ raise ValueError(f"{name} must be {'nonempty ' if required else ''}text")
+ if "\0" in value:
+ raise ValueError(f"{name} cannot contain NUL")
+ return value.encode("utf-8")
+
+
+def _evaluate(
+ rows: Sequence[OrgElementFact],
+ scope_id: int,
+ query_kind: str,
+ field_name: str,
+ field_value: str,
+ expectation: Expectation,
+ expected_count: int,
+ session: AitiaNativeSession,
+) -> OrgContractResult:
+ session._require_owner()
+ if session.closed:
+ raise AitiaNativeError("Aitia native session is closed")
+
+ # Keep the same library handle and process-exclusive Gambit session used
+ # by Aitia. Opening a second dynamic handle can break cleanup/reinit.
+ buffers = []
+
+ def pointer(value: str, name: str, required: bool = False):
+ buffer = ffi.new("char[]", _utf8(value, name=name, required=required))
+ buffers.append(buffer)
+ return buffer
+
+ native_rows = ffi.new("orgize_element_row[]", len(rows))
+ for index, row in enumerate(rows):
+ native_rows[index].id = _integer(
+ row.id, name="row id", minimum=0, maximum=_INT64_MAX
+ )
+ native_rows[index].parent_id = _integer(
+ row.parent_id, name="parent id", minimum=-1, maximum=_INT64_MAX
+ )
+ native_rows[index].kind = pointer(row.kind, "row kind", required=True)
+ native_rows[index].field_name = pointer(row.field_name, "field name")
+ native_rows[index].field_value = pointer(row.field_value, "field value")
+ result = ffi.new("orgize_contract_result *")
+ error = ffi.new("char[512]")
+ status = lib.lambda_aitia_python_session_org_contract(
+ session._handle,
+ native_rows if rows else ffi.NULL,
+ len(rows),
+ scope_id,
+ pointer(query_kind, "query kind", required=True),
+ pointer(field_name, "field name"),
+ pointer(field_value, "field value"),
+ _EXPECTATION_CODE[expectation],
+ expected_count,
+ result,
+ error,
+ 512,
+ )
+ if status != 0 or result.status != 0:
+ detail = ffi.string(error).decode("utf-8", errors="replace")
+ raise AitiaNativeError(detail or "Orgize rejected graph contract evaluation")
+ return OrgContractResult(result.matched_count, result.passed == 1)
+
+
+def evaluate_org_contract(
+ rows: Sequence[OrgElementFact],
+ *,
+ scope_id: int,
+ query_kind: str,
+ field_name: str = "",
+ field_value: str = "",
+ expectation: Expectation = "exactly",
+ expected_count: int = 1,
+ session: AitiaNativeSession | None = None,
+) -> OrgContractResult:
+ """Evaluate an Orgize Contract on source-owner-projected graph facts.
+
+ This result is an observation, not source authenticity, Host admission,
+ review approval, or runtime-effect authority.
+ """
+ if not isinstance(rows, Sequence) or isinstance(rows, (str, bytes)):
+ raise TypeError("rows must be a sequence of OrgElementFact values")
+ if len(rows) > 10000 or any(not isinstance(row, OrgElementFact) for row in rows):
+ raise ValueError("rows must contain at most 10000 OrgElementFact values")
+ _integer(scope_id, name="scope id", minimum=0, maximum=_INT64_MAX)
+ _integer(expected_count, name="expected count", minimum=0, maximum=10000)
+ if expectation not in _EXPECTATION_CODE:
+ raise ValueError("expectation must be exactly, at-least, or at-most")
+ if session is None:
+ with AitiaNativeSession() as owned:
+ return _evaluate(
+ rows, scope_id, query_kind, field_name, field_value,
+ expectation, expected_count, owned,
+ )
+ return _evaluate(
+ rows, scope_id, query_kind, field_name, field_value,
+ expectation, expected_count, session,
+ )
diff --git a/bindings/python/src/lambda_aitia/org_workspace.py b/bindings/python/src/lambda_aitia/org_workspace.py
new file mode 100644
index 0000000..7da207e
--- /dev/null
+++ b/bindings/python/src/lambda_aitia/org_workspace.py
@@ -0,0 +1,169 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+#
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""Source-bound Org edit proposals; Orgize owns parsing and application.
+
+The caller supplies node identities and byte ranges from an Orgize projection.
+This module checks that those ranges still refer to the projected source. It
+does not authenticate the projection, interpret Org syntax, or write a file.
+"""
+
+from dataclasses import dataclass
+import hashlib
+import os
+from pathlib import Path
+import re
+import stat
+from typing import Sequence
+
+
+MAX_ORG_SOURCE_BYTES = 16 * 1024 * 1024
+MAX_ORG_EDITS = 128
+_DIGEST_PATTERN = re.compile(r"sha256:[0-9a-f]{64}\Z")
+
+
+def _read_source(source: Path) -> bytes:
+ if not hasattr(os, "O_NOFOLLOW"):
+ raise OSError("Org source preflight requires O_NOFOLLOW")
+ flags = os.O_RDONLY | os.O_NOFOLLOW
+ if hasattr(os, "O_CLOEXEC"):
+ flags |= os.O_CLOEXEC
+ descriptor = os.open(source, flags)
+ try:
+ with os.fdopen(descriptor, "rb") as stream:
+ descriptor = -1
+ info = os.fstat(stream.fileno())
+ if not stat.S_ISREG(info.st_mode):
+ raise ValueError("Org source must be a regular file")
+ if info.st_size > MAX_ORG_SOURCE_BYTES:
+ raise ValueError("Org source exceeds byte limit")
+ payload = stream.read(MAX_ORG_SOURCE_BYTES + 1)
+ if len(payload) > MAX_ORG_SOURCE_BYTES:
+ raise ValueError("Org source exceeds byte limit")
+ return payload
+ finally:
+ if descriptor >= 0:
+ os.close(descriptor)
+
+
+def org_source_digest(payload: bytes) -> str:
+ """Digest exact Org source bytes, not a parsed or canonicalized tree."""
+ return "sha256:" + hashlib.sha256(payload).hexdigest()
+
+
+@dataclass(frozen=True, slots=True)
+class OrgNodeEdit:
+ """An Orgize-projected node's original byte span and proposed replacement."""
+
+ node_id: str
+ start_byte: int
+ end_byte: int
+ expected_old: bytes
+ replacement: bytes
+
+
+@dataclass(frozen=True, slots=True)
+class OrgEditReview:
+ """Inert proposal for owner review and source-owned application."""
+
+ source_id: str
+ source_digest: str
+ source_size_bytes: int
+ edits: tuple[OrgNodeEdit, ...]
+
+ def source_current(self, source: Path) -> bool:
+ """Recheck bytes before review; application must check again itself."""
+ try:
+ payload = _read_source(Path(source))
+ except (OSError, ValueError):
+ return False
+ return (
+ len(payload) == self.source_size_bytes
+ and org_source_digest(payload) == self.source_digest
+ and all(
+ payload[edit.start_byte : edit.end_byte] == edit.expected_old
+ for edit in self.edits
+ )
+ )
+
+
+def prepare_org_edit_review(
+ source: Path,
+ *,
+ source_id: str,
+ projected_source_digest: str,
+ edits: Sequence[OrgNodeEdit],
+) -> OrgEditReview:
+ """Reject stale/ambiguous Orgize anchors without modifying the source.
+
+ ``projected_source_digest`` must describe the exact bytes used to produce
+ the upstream node IDs and byte ranges. A matching digest is a freshness
+ check, not proof that the caller's projection is trusted or authorized.
+ """
+ if not isinstance(source_id, str) or not source_id.strip():
+ raise ValueError("source_id must be nonempty")
+ if not isinstance(projected_source_digest, str) or not _DIGEST_PATTERN.fullmatch(
+ projected_source_digest
+ ):
+ raise ValueError("projected_source_digest must be a SHA-256 digest")
+ payload = _read_source(Path(source))
+ try:
+ payload.decode("utf-8")
+ except UnicodeDecodeError as error:
+ raise ValueError("Org source must be UTF-8") from error
+ digest = org_source_digest(payload)
+ if digest != projected_source_digest:
+ raise ValueError("Org source changed since projection")
+ if not 1 <= len(edits) <= MAX_ORG_EDITS:
+ raise ValueError("Org edit count must be between 1 and 128")
+ if any(not isinstance(edit, OrgNodeEdit) for edit in edits):
+ raise TypeError("edits must be OrgNodeEdit values")
+ if any(
+ type(edit.start_byte) is not int or type(edit.end_byte) is not int
+ for edit in edits
+ ):
+ raise ValueError("Org node edit has an invalid byte span")
+
+ ordered = sorted(edits, key=lambda edit: (edit.start_byte, edit.end_byte))
+ seen_ids: set[str] = set()
+ prior_end = 0
+ replacement_bytes = 0
+ for edit in ordered:
+ if not isinstance(edit.node_id, str) or not edit.node_id.strip():
+ raise ValueError("node_id must be nonempty")
+ if edit.node_id in seen_ids:
+ raise ValueError("duplicate Org node ID in edit proposal")
+ seen_ids.add(edit.node_id)
+ if (
+ not 0 <= edit.start_byte < edit.end_byte <= len(payload)
+ ):
+ raise ValueError("Org node edit has an invalid byte span")
+ if (
+ edit.start_byte < len(payload)
+ and payload[edit.start_byte] & 0xC0 == 0x80
+ ) or (
+ edit.end_byte < len(payload)
+ and payload[edit.end_byte] & 0xC0 == 0x80
+ ):
+ raise ValueError("Org node edit splits a UTF-8 codepoint")
+ if edit.start_byte < prior_end:
+ raise ValueError("Org node edits overlap")
+ if not isinstance(edit.expected_old, bytes) or not isinstance(
+ edit.replacement, bytes
+ ):
+ raise TypeError("Org node edits require exact source bytes")
+ try:
+ edit.node_id.encode("utf-8")
+ edit.expected_old.decode("utf-8")
+ edit.replacement.decode("utf-8")
+ except UnicodeError as error:
+ raise ValueError("Org node edit must use UTF-8") from error
+ replacement_bytes += len(edit.replacement)
+ if replacement_bytes > MAX_ORG_SOURCE_BYTES:
+ raise ValueError("Org replacements exceed byte limit")
+ if payload[edit.start_byte : edit.end_byte] != edit.expected_old:
+ raise ValueError("Org node moved or its expected content changed")
+ prior_end = edit.end_byte
+
+ return OrgEditReview(source_id, digest, len(payload), tuple(ordered))
diff --git a/bindings/python/src/lambda_aitia/runtime.py b/bindings/python/src/lambda_aitia/runtime.py
index d7d007b..2bc6312 100644
--- a/bindings/python/src/lambda_aitia/runtime.py
+++ b/bindings/python/src/lambda_aitia/runtime.py
@@ -9,7 +9,7 @@
from dataclasses import dataclass
from typing import Any, Mapping
-from .native import AitiaNativeError, sdlc_flow_plan
+from .native import AitiaNativeError, AitiaNativeSession, sdlc_flow_plan
def _string(value: Any, field: str) -> str:
@@ -87,9 +87,14 @@ class SdlcRuntime:
complete until every Scheme-owned stage publishes an attributable receipt.
"""
+ def __init__(self, *, session: AitiaNativeSession | None = None) -> None:
+ self._session = session
+
def plan(self, lifecycle_id: str) -> SdlcFlowPlan:
"""Build and validate the canonical POO Flow DAG for a lifecycle."""
if not isinstance(lifecycle_id, str) or not lifecycle_id:
raise ValueError("lifecycle_id must be a non-empty string")
- return SdlcFlowPlan.from_payload(sdlc_flow_plan(lifecycle_id))
+ return SdlcFlowPlan.from_payload(
+ sdlc_flow_plan(lifecycle_id, session=self._session)
+ )
diff --git a/bindings/python/src/lambda_aitia/verification.py b/bindings/python/src/lambda_aitia/verification.py
new file mode 100644
index 0000000..13e94fe
--- /dev/null
+++ b/bindings/python/src/lambda_aitia/verification.py
@@ -0,0 +1,422 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+#
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""Observable pytest execution, not an Assurance Host admission."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, replace
+import hashlib
+import os
+from pathlib import Path, PurePosixPath, PureWindowsPath
+import selectors
+import signal
+import subprocess
+import tempfile
+import time
+from uuid import uuid4
+import xml.etree.ElementTree as ET
+
+import anyio
+
+
+_SOURCE_MAGIC = b"lambda-aitia.source-tree.v1\0"
+_MAX_SOURCE_BYTES = 64 * 1024 * 1024
+_MAX_SOURCE_FILES = 10_000
+
+
+def _source_payload_digest(payload: bytes) -> str:
+ return "sha256:" + hashlib.sha256(payload).hexdigest()
+
+
+def capture_source_tree(root: Path) -> bytes:
+ """Freeze a regular-file tree into canonical, source-lock-compatible bytes.
+
+ Source paths and contents are length-framed, ordered, and bounded. The
+ returned bytes can be independently rehashed by the Scheme Host's source
+ reader; their digest is not a verification seal.
+ """
+
+ root = root.resolve(strict=True)
+ if not root.is_dir():
+ raise ValueError("source root must be a directory")
+ entries: list[tuple[bytes, bytes]] = []
+ size = len(_SOURCE_MAGIC) + 8
+ for path in root.rglob("*"):
+ if path.is_symlink():
+ raise ValueError(f"source tree contains a symlink: {path}")
+ if path.is_dir():
+ continue
+ if not path.is_file():
+ raise ValueError(f"source tree contains a non-file: {path}")
+ relative = path.relative_to(root).as_posix().encode("utf-8")
+ remaining = _MAX_SOURCE_BYTES - size - 4 - len(relative) - 8
+ if remaining < 0:
+ raise ValueError("source tree exceeds verifier capture limit")
+ with path.open("rb") as source:
+ contents = source.read(remaining + 1)
+ size += 4 + len(relative) + 8 + len(contents)
+ if size > _MAX_SOURCE_BYTES or len(entries) >= _MAX_SOURCE_FILES:
+ raise ValueError("source tree exceeds verifier capture limit")
+ entries.append((relative, contents))
+ entries.sort(key=lambda entry: entry[0])
+ framed = bytearray(_SOURCE_MAGIC)
+ framed.extend(len(entries).to_bytes(8, "big"))
+ for relative, contents in entries:
+ framed.extend(len(relative).to_bytes(4, "big"))
+ framed.extend(relative)
+ framed.extend(len(contents).to_bytes(8, "big"))
+ framed.extend(contents)
+ return bytes(framed)
+
+
+def _source_entries(payload: bytes) -> tuple[tuple[Path, bytes], ...]:
+ if len(payload) > _MAX_SOURCE_BYTES or not payload.startswith(_SOURCE_MAGIC):
+ raise ValueError("invalid frozen source payload")
+ position = len(_SOURCE_MAGIC)
+
+ def take(length: int) -> bytes:
+ nonlocal position
+ if length < 0 or position + length > len(payload):
+ raise ValueError("truncated frozen source payload")
+ result = payload[position : position + length]
+ position += length
+ return result
+
+ count = int.from_bytes(take(8), "big")
+ if count > _MAX_SOURCE_FILES:
+ raise ValueError("frozen source file count exceeds limit")
+ entries: list[tuple[Path, bytes]] = []
+ previous = b""
+ for _ in range(count):
+ relative = take(int.from_bytes(take(4), "big"))
+ if not relative or relative <= previous or b"\\" in relative or b"\0" in relative:
+ raise ValueError("invalid frozen source path ordering")
+ previous = relative
+ try:
+ decoded = relative.decode("utf-8")
+ except UnicodeDecodeError as error:
+ raise ValueError("invalid frozen source path encoding") from error
+ path = PurePosixPath(decoded)
+ if (
+ path.is_absolute()
+ or PureWindowsPath(decoded).drive
+ or any(part in ("", ".", "..") for part in decoded.split("/"))
+ ):
+ raise ValueError("frozen source path escapes root")
+ contents = take(int.from_bytes(take(8), "big"))
+ entries.append((Path(*path.parts), contents))
+ if position != len(payload):
+ raise ValueError("trailing frozen source payload bytes")
+ return tuple(entries)
+
+
+def source_digest(root: Path) -> str:
+ """Hash the canonical frozen bytes of a regular-file tree."""
+
+ return _source_payload_digest(capture_source_tree(root))
+
+
+@dataclass(frozen=True, slots=True)
+class PytestObservation:
+ """One subprocess observation; this value carries no issuing authority."""
+
+ run_id: str
+ source_digest: str
+ argv: tuple[str, ...]
+ exit_code: int | None
+ status: str
+ collected: int
+ passed: int
+ skipped: int
+ report_digest: str | None
+ output: str
+
+ @property
+ def passed_obligation(self) -> bool:
+ return self.status == "passed"
+
+ def source_current(self, root: Path) -> bool:
+ try:
+ return source_digest(root) == self.source_digest
+ except (OSError, ValueError):
+ return False
+
+
+def _validated_selectors(
+ selectors: tuple[str, ...], entries: tuple[tuple[Path, bytes], ...]
+) -> None:
+ selected_files = {path.as_posix() for path, _ in entries}
+ if not selectors:
+ raise ValueError("at least one explicit pytest selector is required")
+ for selector in selectors:
+ filename = selector.split("::", 1)[0]
+ if (
+ not filename
+ or selector.startswith("-")
+ or "\\" in filename
+ or "\0" in filename
+ or any(part in ("", ".", "..") for part in filename.split("/"))
+ or filename not in selected_files
+ ):
+ raise ValueError("pytest selector must name a file in frozen source")
+
+
+def _stop_process_group(process: subprocess.Popen[bytes]) -> bool:
+ """Stop pytest's group; return false if descendant cleanup is uncertain."""
+
+ controlled = True
+ try:
+ os.killpg(process.pid, signal.SIGTERM)
+ except ProcessLookupError:
+ pass
+ except PermissionError:
+ controlled = False
+ try:
+ process.wait(timeout=0.2)
+ except subprocess.TimeoutExpired:
+ pass
+ try:
+ os.killpg(process.pid, signal.SIGKILL)
+ except ProcessLookupError:
+ pass
+ except PermissionError:
+ controlled = False
+ try:
+ process.wait(timeout=1)
+ except subprocess.TimeoutExpired:
+ controlled = False
+ process.kill()
+ process.wait()
+ return controlled
+
+
+def _run_bounded_verifier(
+ argv: tuple[str, ...],
+ root: Path,
+ environment: dict[str, str],
+ timeout_seconds: int,
+ max_output_bytes: int,
+) -> tuple[int | None, str, str | None]:
+ """Drain bounded output and terminate the whole verifier group on failure.
+
+ Non-POSIX process trees are rejected until an equivalent descendant-kill
+ contract is qualified; silently killing only pytest would leave effects.
+ """
+
+ if os.name != "posix":
+ raise OSError("controlled pytest verification requires POSIX process groups")
+ tail = bytearray()
+ observed_bytes = 0
+ deadline = time.monotonic() + timeout_seconds
+ stop_reason = None
+ process = subprocess.Popen(
+ argv,
+ cwd=root,
+ env=environment,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ start_new_session=True,
+ )
+ try:
+ assert process.stdout is not None
+ with selectors.DefaultSelector() as selector:
+ selector.register(process.stdout, selectors.EVENT_READ)
+ output_complete = False
+ while not (output_complete and process.poll() is not None):
+ remaining = deadline - time.monotonic()
+ if remaining <= 0:
+ stop_reason = "timeout"
+ break
+ for key, _ in selector.select(timeout=min(remaining, 0.1)):
+ chunk = os.read(key.fileobj.fileno(), 65536)
+ if not chunk:
+ selector.unregister(key.fileobj)
+ output_complete = True
+ continue
+ observed_bytes += len(chunk)
+ tail.extend(chunk)
+ del tail[:-8192]
+ if observed_bytes > max_output_bytes:
+ stop_reason = "output-exceeded"
+ break
+ if stop_reason:
+ break
+ if stop_reason:
+ if not _stop_process_group(process):
+ stop_reason = "control-error"
+ exit_code = None
+ else:
+ exit_code = process.wait()
+ return exit_code, tail.decode("utf-8", "replace"), stop_reason
+ finally:
+ if process.poll() is None:
+ _stop_process_group(process)
+ if process.stdout is not None:
+ process.stdout.close()
+
+
+def run_pytest_frozen(
+ python: Path,
+ source_payload: bytes,
+ selectors: tuple[str, ...],
+ *,
+ timeout_seconds: int = 30,
+ max_output_bytes: int = 8 * 1024 * 1024,
+ environment: dict[str, str] | None = None,
+) -> PytestObservation:
+ """Run selected tests only from a validated frozen source payload.
+
+ The caller owns the interpreter, dependencies and external services. The
+ payload is observable process input, not a Host-issued verification seal.
+ """
+
+ entries = _source_entries(source_payload)
+ _validated_selectors(selectors, entries)
+ if timeout_seconds <= 0:
+ raise ValueError("timeout_seconds must be positive")
+ if max_output_bytes <= 0:
+ raise ValueError("max_output_bytes must be positive")
+ before = _source_payload_digest(source_payload)
+ run_id = uuid4().hex
+ interpreter = python.absolute()
+ if not interpreter.is_file():
+ raise ValueError("Python interpreter is absent")
+ safe_environment = os.environ.copy()
+ for name in ("PYTHONPATH", "PYTEST_ADDOPTS", "PYTEST_PLUGINS"):
+ safe_environment.pop(name, None)
+ safe_environment["PYTEST_DISABLE_PLUGIN_AUTOLOAD"] = "1"
+ safe_environment["PYTHONDONTWRITEBYTECODE"] = "1"
+ if environment:
+ if any(not key.startswith("AITIA_QUALIFICATION_") for key in environment):
+ raise ValueError("only AITIA_QUALIFICATION_ environment values are allowed")
+ safe_environment.update(environment)
+ with tempfile.TemporaryDirectory(prefix="aitia-pytest-") as workspace:
+ root = Path(workspace) / "source"
+ root.mkdir()
+ for relative, contents in entries:
+ target = root / relative
+ target.parent.mkdir(parents=True, exist_ok=True)
+ target.write_bytes(contents)
+ report = Path(workspace) / f"{run_id}.xml"
+ argv = (
+ str(interpreter), "-m", "pytest", "-q", "-s",
+ "-p", "no:cacheprovider", f"--junitxml={report}", *selectors,
+ )
+ exit_code, output, stop_reason = _run_bounded_verifier(
+ argv, root, safe_environment, timeout_seconds, max_output_bytes
+ )
+ try:
+ after = source_digest(root)
+ except (OSError, ValueError):
+ after = None
+ collected = passed = skipped = 0
+ report_digest = None
+ report_valid = False
+ if report.is_file():
+ report_bytes = report.read_bytes()
+ report_digest = "sha256:" + hashlib.sha256(report_bytes).hexdigest()
+ try:
+ suites = ET.fromstring(report_bytes)
+ cases = suites.findall(".//testcase")
+ collected = len(cases)
+ skipped = sum(case.find("skipped") is not None for case in cases)
+ failed = sum(
+ case.find("failure") is not None or case.find("error") is not None
+ for case in cases
+ )
+ passed = collected - skipped - failed
+ report_valid = suites.tag in ("testsuite", "testsuites")
+ except ET.ParseError:
+ pass
+ if before != after:
+ status = "source-changed"
+ elif stop_reason:
+ status = stop_reason
+ elif exit_code == 2:
+ status = "interrupted"
+ elif exit_code == 3:
+ status = "internal-error"
+ elif exit_code == 4:
+ status = "usage-error"
+ elif exit_code == 6:
+ status = "warnings-exceeded"
+ elif not report_valid:
+ status = "report-invalid"
+ elif collected == 0:
+ status = "no-tests"
+ elif exit_code != 0 or passed + skipped != collected:
+ status = "failed"
+ elif passed == 0:
+ status = "no-passing-tests"
+ elif skipped:
+ status = "skipped"
+ else:
+ status = "passed"
+ return PytestObservation(
+ run_id, before, argv, exit_code, status, collected, passed,
+ skipped, report_digest, output[-8192:],
+ )
+
+
+async def run_pytest_frozen_async(
+ python: Path,
+ source_payload: bytes,
+ selectors: tuple[str, ...],
+ *,
+ timeout_seconds: int = 30,
+ max_output_bytes: int = 8 * 1024 * 1024,
+ environment: dict[str, str] | None = None,
+ limiter: anyio.CapacityLimiter | None = None,
+) -> PytestObservation:
+ """Run a frozen verifier through AnyIO without abandoning its process.
+
+ Cancellation waits for the bounded worker to terminate and clean up the
+ pytest process group. No thread-affine native session enters this worker;
+ the result remains an observation, not a Host admission.
+ """
+
+ frozen_payload = bytes(source_payload)
+ frozen_selectors = tuple(selectors)
+ frozen_environment = dict(environment) if environment is not None else None
+
+ def execute() -> PytestObservation:
+ return run_pytest_frozen(
+ python,
+ frozen_payload,
+ frozen_selectors,
+ timeout_seconds=timeout_seconds,
+ max_output_bytes=max_output_bytes,
+ environment=frozen_environment,
+ )
+
+ return await anyio.to_thread.run_sync(
+ execute, abandon_on_cancel=False, limiter=limiter
+ )
+
+
+def run_pytest(
+ python: Path,
+ source_root: Path,
+ selectors: tuple[str, ...],
+ *,
+ timeout_seconds: int = 30,
+ max_output_bytes: int = 8 * 1024 * 1024,
+ environment: dict[str, str] | None = None,
+) -> PytestObservation:
+ """Capture source, execute its frozen copy, and detect caller-tree drift.
+
+ A changing caller tree invalidates the observation, but even a passing
+ observation is not a Scheme Assurance Host admission.
+ """
+
+ payload = capture_source_tree(source_root)
+ observation = run_pytest_frozen(
+ python, payload, selectors,
+ timeout_seconds=timeout_seconds, max_output_bytes=max_output_bytes,
+ environment=environment,
+ )
+ if not observation.source_current(source_root):
+ return replace(observation, status="source-changed")
+ return observation
diff --git a/bindings/python/tests/installed_wheel_qualification.py b/bindings/python/tests/installed_wheel_qualification.py
new file mode 100644
index 0000000..205344b
--- /dev/null
+++ b/bindings/python/tests/installed_wheel_qualification.py
@@ -0,0 +1,107 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+#
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""Project qualification of an installed wheel outside the checkout."""
+
+from __future__ import annotations
+
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import tempfile
+
+
+PROJECT = Path(__file__).resolve().parents[3]
+PYTHON = PROJECT / "bindings/python/.venv/bin/python"
+WHEELS = PROJECT / "bindings/python/dist"
+SCENARIO = PROJECT / "bindings/python/qualification/job_executor"
+
+
+def run(
+ *argv: str,
+ cwd: Path,
+ environment: dict[str, str] | None = None,
+ quiet: bool = False,
+) -> None:
+ subprocess.run(
+ argv, cwd=cwd, env=environment, check=True, timeout=90,
+ stdout=subprocess.DEVNULL if quiet else None,
+ )
+
+
+def test_installed_wheel_qualification() -> None:
+ wheels = list(WHEELS.glob("lambda_aitia-*.whl"))
+ if not wheels:
+ raise RuntimeError("build the Aitia wheel before qualification")
+ wheel = max(wheels, key=lambda path: path.stat().st_mtime_ns)
+ with tempfile.TemporaryDirectory(prefix="aitia-installed-wheel-") as directory:
+ root = Path(directory)
+ venv = root / "venv"
+ scenario = root / "scenario"
+ requirements = root / "requirements.txt"
+ shutil.copytree(SCENARIO, scenario)
+ run("uv", "export", "--offline", "--locked", "--extra", "test",
+ "--no-emit-project", "--output-file", str(requirements),
+ cwd=PROJECT / "bindings/python", quiet=True)
+ run("uv", "venv", "--python", str(PYTHON), str(venv), cwd=root)
+ interpreter = venv / "bin/python"
+ # A fresh CI cache can lack a locked wheel even when the source-tree
+ # environment already has it. Resolve only hashes from uv.lock;
+ # reuse cached wheels and fetch missing ones when needed.
+ run("uv", "pip", "install", "--python", str(interpreter),
+ "-r", str(requirements), cwd=root)
+ run("uv", "pip", "install", "--offline", "--no-deps", "--python",
+ str(interpreter), str(wheel), cwd=root)
+ environment = os.environ.copy()
+ for name in ("PYTHONPATH", "LAMBDA_AITIA_NATIVE_LIBRARY", "VIRTUAL_ENV"):
+ environment.pop(name, None)
+ # Import, native invocation, verifier and stale-source test all occur
+ # with cwd outside the checkout and no development path override.
+ code = """
+import anyio
+from pathlib import Path
+from lambda_aitia import (AitiaNativeSession, OrgElementFact, SdlcRuntime,
+ evaluate_org_contract)
+from lambda_aitia.verification import capture_source_tree, run_pytest, run_pytest_frozen_async
+root = Path('scenario').resolve()
+assert SdlcRuntime().plan('qualification/job-42').lifecycle_id == 'qualification/job-42'
+with AitiaNativeSession() as session:
+ runtime = SdlcRuntime(session=session)
+ assert runtime.plan('qualification/job-42/first').lifecycle_id == 'qualification/job-42/first'
+ assert runtime.plan('qualification/job-42/second').lifecycle_id == 'qualification/job-42/second'
+ graph = (OrgElementFact(0, -1, 'org-data'),
+ OrgElementFact(1, 0, 'headline', 'title', 'Evidence'))
+ org_result = evaluate_org_contract(graph, scope_id=0, query_kind='headline',
+ field_name='title', field_value='Evidence',
+ session=session)
+ assert org_result.passed and org_result.matched_count == 1
+assert session.closed
+selector = ('test_unique_effect.py::test_unique_effect_after_lost_ack_and_restart',)
+buggy = run_pytest(Path(__import__('sys').executable), root, selector,
+ environment={'AITIA_QUALIFICATION_MODE': 'buggy'})
+assert buggy.status == 'failed', buggy
+assert 'assert 2 == 1' in buggy.output, buggy.output
+source_payload = capture_source_tree(root)
+
+async def verify_fixed():
+ return await run_pytest_frozen_async(Path(__import__('sys').executable),
+ source_payload, selector,
+ environment={'AITIA_QUALIFICATION_MODE': 'fixed'},
+ limiter=anyio.CapacityLimiter(1))
+
+with AitiaNativeSession() as session:
+ runtime = SdlcRuntime(session=session)
+ assert runtime.plan('qualification/job-42/first').lifecycle_id == 'qualification/job-42/first'
+ fixed = anyio.run(verify_fixed)
+ assert fixed.passed_obligation and fixed.collected == 1, fixed
+ assert runtime.plan('qualification/job-42/second').lifecycle_id == 'qualification/job-42/second'
+assert session.closed
+assert fixed.source_current(root)
+worker = root / 'worker.py'
+worker.write_bytes(worker.read_bytes() + b'\\n# changed after verification\\n')
+assert not fixed.source_current(root)
+print('installed-wheel-qualification: buggy=reproduced fixed=passed stale=rejected')
+"""
+ run(str(interpreter), "-c", code, cwd=root, environment=environment)
diff --git a/bindings/python/tests/test_native.py b/bindings/python/tests/test_native.py
index d9fd888..235be48 100644
--- a/bindings/python/tests/test_native.py
+++ b/bindings/python/tests/test_native.py
@@ -4,7 +4,13 @@
import pytest
-from lambda_aitia import GitOpsDecision, SdlcRuntime, descriptor, evaluate_gitops
+from lambda_aitia import (
+ AitiaNativeSession,
+ GitOpsDecision,
+ SdlcRuntime,
+ descriptor,
+ evaluate_gitops,
+)
from lambda_aitia.native import AitiaNativeError
from lambda_aitia.runtime import SdlcFlowPlan
@@ -42,6 +48,33 @@ def test_runtime_rejects_invalid_lifecycle_before_native_dispatch() -> None:
SdlcRuntime().plan("")
+def test_session_keeps_one_scheme_runtime_for_distinct_calls() -> None:
+ with AitiaNativeSession() as session:
+ runtime = SdlcRuntime(session=session)
+ assert runtime.plan("session/first").lifecycle_id == "session/first"
+ assert runtime.plan("session/second").lifecycle_id == "session/second"
+ assert evaluate_gitops(_gitops_change(), session=session).accepted is True
+ with pytest.raises(AitiaNativeError, match="active session"):
+ AitiaNativeSession()
+ with pytest.raises(AitiaNativeError, match="active session"):
+ descriptor()
+ assert session.closed
+ session.close()
+ with pytest.raises(AitiaNativeError, match="closed"):
+ runtime.plan("session/after-close")
+ assert descriptor()["semanticOwner"] == "lambda-aitia"
+
+
+def test_session_rejects_cross_thread_calls() -> None:
+ from concurrent.futures import ThreadPoolExecutor
+
+ with AitiaNativeSession() as session:
+ with ThreadPoolExecutor(max_workers=1) as pool:
+ failure = pool.submit(SdlcRuntime(session=session).plan, "other/thread")
+ with pytest.raises(AitiaNativeError, match="another thread"):
+ failure.result()
+
+
@pytest.mark.parametrize("field", ["releaseAuthorized", "runtimeExecuted"])
def test_runtime_rejects_inert_plan_claiming_effect(field: str) -> None:
payload = {
@@ -62,23 +95,30 @@ def test_runtime_rejects_inert_plan_claiming_effect(field: str) -> None:
SdlcFlowPlan.from_payload(payload)
+def _gitops_change() -> dict[str, object]:
+ return {
+ "event": "pull-request",
+ "repository": "tao3k/poo-flow",
+ "revision": "0123456789abcdef",
+ "source-ref": "feature/aitia",
+ "target-ref": "develop",
+ "pull-request": 42,
+ "checks": [
+ {"name": "commit-policy", "conclusion": "success"},
+ {"name": "build", "conclusion": "success"},
+ {"name": "unit-test", "conclusion": "success"},
+ {
+ "name": "nasa/sdlc/npr-7150.2",
+ "conclusion": "success",
+ "standardEdition": "D",
+ "sourceLockDigest": "sha256:cfb963b8cd81fd8e22e9b47251c1dc7927b85fcdf9ec9366057cd38cb94f338c",
+ },
+ ],
+ }
+
+
def test_python_delegates_gitops_decision_to_scheme() -> None:
- decision = evaluate_gitops(
- {
- "event": "pull-request",
- "repository": "tao3k/poo-flow",
- "revision": "0123456789abcdef",
- "source-ref": "feature/aitia",
- "target-ref": "develop",
- "pull-request": 42,
- "checks": [
- {"name": "commit-policy", "conclusion": "success"},
- {"name": "build", "conclusion": "success"},
- {"name": "unit-test", "conclusion": "success"},
- {"name": "nasa-7150-2d", "conclusion": "success"},
- ],
- }
- )
+ decision = evaluate_gitops(_gitops_change())
assert isinstance(decision, GitOpsDecision)
assert decision.accepted is True
assert decision.profile == "dev"
@@ -86,3 +126,28 @@ def test_python_delegates_gitops_decision_to_scheme() -> None:
assert decision.authority_status == "not-evaluated"
assert decision.release_authorized is False
assert decision.runtime_executed is False
+
+
+@pytest.mark.parametrize(
+ ("field", "value"),
+ [
+ ("standardEdition", "C"),
+ ("sourceLockDigest", "sha256:wrong-lock"),
+ ("sourceLockDigest", None),
+ ],
+)
+def test_python_rejects_stale_standard_assessment(field: str, value: str | None) -> None:
+ change = _gitops_change()
+ checks = change["checks"]
+ assert isinstance(checks, list)
+ standard_check = checks[-1]
+ assert isinstance(standard_check, dict)
+ if value is None:
+ standard_check.pop(field)
+ else:
+ standard_check[field] = value
+
+ decision = evaluate_gitops(change)
+ assert decision.accepted is False
+ assert decision.stale_checks == ("nasa/sdlc/npr-7150.2",)
+ assert decision.reasons == ("standard-assessment-mismatch",)
diff --git a/bindings/python/tests/test_org_contract.py b/bindings/python/tests/test_org_contract.py
new file mode 100644
index 0000000..413131b
--- /dev/null
+++ b/bindings/python/tests/test_org_contract.py
@@ -0,0 +1,58 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+#
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""The delivered Aitia library calls Orgize's Scheme-owned C ABI."""
+
+import pytest
+
+from lambda_aitia import (
+ AitiaNativeSession,
+ OrgElementFact,
+ evaluate_org_contract,
+)
+from lambda_aitia.native import AitiaNativeError
+
+
+ROWS = (
+ OrgElementFact(0, -1, "org-data"),
+ OrgElementFact(1, 0, "headline", "title", "Evidence"),
+)
+
+
+def test_orgize_contract_through_bundled_native_library() -> None:
+ result = evaluate_org_contract(
+ ROWS, scope_id=0, query_kind="headline", field_name="title",
+ field_value="Evidence",
+ )
+ assert result.matched_count == 1
+ assert result.passed is True
+ assert evaluate_org_contract(
+ ROWS, scope_id=0, query_kind="headline", field_name="title",
+ field_value="Missing",
+ ).passed is False
+
+
+def test_orgize_contract_uses_existing_native_session() -> None:
+ with AitiaNativeSession() as session:
+ result = evaluate_org_contract(
+ ROWS, scope_id=0, query_kind="headline", session=session,
+ )
+ assert result.matched_count == 1
+ with pytest.raises(AitiaNativeError, match="closed"):
+ evaluate_org_contract(
+ ROWS, scope_id=0, query_kind="headline", session=session,
+ )
+
+
+@pytest.mark.parametrize(
+ ("rows", "kind", "error"),
+ [
+ ((OrgElementFact(0, -1, "head\x00line"),), "headline", "NUL"),
+ ((OrgElementFact(-1, -1, "headline"),), "headline", "row id"),
+ (ROWS, "head\x00line", "NUL"),
+ ],
+)
+def test_orgize_transport_rejects_unsafe_inputs(rows, kind, error) -> None:
+ with pytest.raises(ValueError, match=error):
+ evaluate_org_contract(rows, scope_id=0, query_kind=kind)
diff --git a/bindings/python/tests/test_org_workspace.py b/bindings/python/tests/test_org_workspace.py
new file mode 100644
index 0000000..19ec0e5
--- /dev/null
+++ b/bindings/python/tests/test_org_workspace.py
@@ -0,0 +1,178 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+#
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""Aitia preflight uses Orgize anchors; it never parses or mutates Org."""
+
+import hashlib
+from pathlib import Path
+
+import pytest
+
+from lambda_aitia import OrgNodeEdit, prepare_org_edit_review
+
+
+SOURCE = b"* Design\n:PROPERTIES:\n:ID: design-1\n:END:\nOld guarantee\n* Notes\nKeep me\n"
+
+
+def _digest(payload: bytes) -> str:
+ return "sha256:" + hashlib.sha256(payload).hexdigest()
+
+
+def _edit(payload: bytes = SOURCE, target: bytes = b"Old guarantee") -> OrgNodeEdit:
+ start = payload.index(target)
+ return OrgNodeEdit("design-1", start, start + len(target), target, b"New guarantee")
+
+
+def test_prepares_source_bound_review_without_writing(tmp_path: Path) -> None:
+ source = tmp_path / "design.org"
+ source.write_bytes(SOURCE)
+ review = prepare_org_edit_review(
+ source,
+ source_id="design.org",
+ projected_source_digest=_digest(SOURCE),
+ edits=(_edit(),),
+ )
+ assert review.source_id == "design.org"
+ assert review.source_digest == _digest(SOURCE)
+ assert review.edits == (_edit(),)
+ assert review.source_current(source)
+ assert source.read_bytes() == SOURCE
+
+
+def test_preflights_aitia_maintained_workspace_without_mutating_it() -> None:
+ source = Path(__file__).resolve().parents[3] / "user-interface/workspaces/job-executor.org"
+ payload = source.read_bytes()
+ expected = b"This subtree is the review context for ADR 0008."
+ start = payload.index(expected)
+ review = prepare_org_edit_review(
+ source,
+ source_id="job-executor",
+ projected_source_digest=_digest(payload),
+ edits=(
+ OrgNodeEdit(
+ "aitia-job-executor-design-decision",
+ start,
+ start + len(expected),
+ expected,
+ b"This subtree requires renewed review for ADR 0008.",
+ ),
+ ),
+ )
+ assert review.source_current(source)
+ assert source.read_bytes() == payload
+
+
+def test_rejects_stale_projection_even_when_old_text_remains(tmp_path: Path) -> None:
+ source = tmp_path / "design.org"
+ source.write_bytes(SOURCE + b"* Later\n")
+ with pytest.raises(ValueError, match="changed since projection"):
+ prepare_org_edit_review(
+ source,
+ source_id="design.org",
+ projected_source_digest=_digest(SOURCE),
+ edits=(_edit(),),
+ )
+
+
+def test_rejects_moved_span_with_fresh_digest(tmp_path: Path) -> None:
+ source = tmp_path / "design.org"
+ moved = SOURCE.replace(b"Old guarantee", b"prefix Old guarantee")
+ source.write_bytes(moved)
+ with pytest.raises(ValueError, match="moved or its expected content changed"):
+ prepare_org_edit_review(
+ source,
+ source_id="design.org",
+ projected_source_digest=_digest(moved),
+ edits=(_edit(),),
+ )
+
+
+def test_rejects_overlapping_edits_and_duplicate_node(tmp_path: Path) -> None:
+ source = tmp_path / "design.org"
+ source.write_bytes(SOURCE)
+ first = _edit()
+ second = OrgNodeEdit(
+ "design-2",
+ first.start_byte + 1,
+ first.end_byte,
+ SOURCE[first.start_byte + 1 : first.end_byte],
+ b"changed",
+ )
+ with pytest.raises(ValueError, match="overlap"):
+ prepare_org_edit_review(
+ source, source_id="design.org", projected_source_digest=_digest(SOURCE),
+ edits=(first, second),
+ )
+ with pytest.raises(ValueError, match="duplicate Org node ID"):
+ prepare_org_edit_review(
+ source, source_id="design.org", projected_source_digest=_digest(SOURCE),
+ edits=(first, first),
+ )
+
+
+def test_review_goes_stale_after_preflight(tmp_path: Path) -> None:
+ source = tmp_path / "design.org"
+ source.write_bytes(SOURCE)
+ review = prepare_org_edit_review(
+ source, source_id="design.org", projected_source_digest=_digest(SOURCE),
+ edits=(_edit(),),
+ )
+ source.write_bytes(SOURCE.replace(b"Keep me", b"Changed"))
+ assert not review.source_current(source)
+
+
+def test_refuses_symlink_and_invalid_byte_span(tmp_path: Path) -> None:
+ source = tmp_path / "design.org"
+ source.write_bytes(SOURCE)
+ alias = tmp_path / "alias.org"
+ alias.symlink_to(source)
+ with pytest.raises(OSError):
+ prepare_org_edit_review(
+ alias, source_id="design.org", projected_source_digest=_digest(SOURCE),
+ edits=(_edit(),),
+ )
+ bad = OrgNodeEdit("design-1", True, 3, b"* D", b"new")
+ with pytest.raises(ValueError, match="invalid byte span"):
+ prepare_org_edit_review(
+ source, source_id="design.org", projected_source_digest=_digest(SOURCE),
+ edits=(bad,),
+ )
+
+
+def test_refuses_non_utf8_source_and_split_codepoint(tmp_path: Path) -> None:
+ source = tmp_path / "design.org"
+ source.write_bytes(SOURCE + b"\xff")
+ with pytest.raises(ValueError, match="source must be UTF-8"):
+ prepare_org_edit_review(
+ source, source_id="design.org",
+ projected_source_digest=_digest(source.read_bytes()), edits=(_edit(),),
+ )
+ unicode_source = SOURCE + "说明\n".encode()
+ source.write_bytes(unicode_source)
+ start = unicode_source.index("说明".encode()) + 1
+ split = OrgNodeEdit("design-1", start, start + 1, b"\x01", b"x")
+ with pytest.raises(ValueError, match="splits a UTF-8 codepoint"):
+ prepare_org_edit_review(
+ source, source_id="design.org",
+ projected_source_digest=_digest(unicode_source), edits=(split,),
+ )
+
+
+def test_refuses_non_utf8_replacement_and_excess_edit_count(tmp_path: Path) -> None:
+ source = tmp_path / "design.org"
+ source.write_bytes(SOURCE)
+ edit = _edit()
+ invalid = OrgNodeEdit(
+ edit.node_id, edit.start_byte, edit.end_byte, edit.expected_old, b"\xff",
+ )
+ with pytest.raises(ValueError, match="edit must use UTF-8"):
+ prepare_org_edit_review(
+ source, source_id="design.org", projected_source_digest=_digest(SOURCE),
+ edits=(invalid,),
+ )
+ with pytest.raises(ValueError, match="edit count"):
+ prepare_org_edit_review(
+ source, source_id="design.org", projected_source_digest=_digest(SOURCE),
+ edits=(edit,) * 129,
+ )
diff --git a/bindings/python/tests/test_verification.py b/bindings/python/tests/test_verification.py
new file mode 100644
index 0000000..817ee82
--- /dev/null
+++ b/bindings/python/tests/test_verification.py
@@ -0,0 +1,221 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+#
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+from pathlib import Path
+import hashlib
+import os
+import sys
+import time
+
+import anyio
+import pytest
+
+from lambda_aitia.verification import (
+ capture_source_tree,
+ run_pytest,
+ run_pytest_frozen,
+ run_pytest_frozen_async,
+ source_digest,
+)
+
+
+@pytest.mark.parametrize(
+ ("body", "status"),
+ [
+ ("def test_ok():\n assert True\n", "passed"),
+ ("def test_no():\n assert False\n", "failed"),
+ ("import pytest\n@pytest.mark.skip\ndef test_skip():\n pass\n", "no-passing-tests"),
+ ],
+)
+def test_pytest_observation_classifies_execution(
+ tmp_path: Path, body: str, status: str
+) -> None:
+ (tmp_path / "test_case.py").write_text(body)
+ observed = run_pytest(Path(sys.executable), tmp_path, ("test_case.py",))
+ assert observed.status == status
+ assert observed.passed_obligation is (status == "passed")
+ assert observed.collected == 1
+ assert observed.report_digest is not None
+ assert observed.source_current(tmp_path)
+
+
+def test_pytest_observation_rejects_no_collection(tmp_path: Path) -> None:
+ (tmp_path / "test_case.py").write_text("x = 1\n")
+ observed = run_pytest(Path(sys.executable), tmp_path, ("test_case.py",))
+ assert observed.status == "no-tests"
+ assert observed.exit_code == 5
+
+
+def test_pytest_observation_detects_source_drift(tmp_path: Path) -> None:
+ source = tmp_path / "test_case.py"
+ source.write_text(
+ "from pathlib import Path\n"
+ "def test_change():\n"
+ " Path(__file__).write_text('changed')\n"
+ )
+ observed = run_pytest(Path(sys.executable), tmp_path, ("test_case.py",))
+ assert observed.status == "source-changed"
+ assert not observed.passed_obligation
+ # The verifier mutated only the frozen execution tree, not caller source.
+ assert observed.source_current(tmp_path)
+ assert source.read_text().startswith("from pathlib import Path")
+
+
+def test_frozen_execution_uses_captured_bytes_not_later_source(tmp_path: Path) -> None:
+ source = tmp_path / "test_case.py"
+ source.write_text("def test_ok():\n assert True\n")
+ payload = capture_source_tree(tmp_path)
+ assert source_digest(tmp_path) == "sha256:" + hashlib.sha256(payload).hexdigest()
+ source.write_text("def test_no():\n assert False\n")
+ observed = run_pytest_frozen(Path(sys.executable), payload, ("test_case.py",))
+ assert observed.status == "passed"
+ assert not observed.source_current(tmp_path)
+ assert observed.source_digest != source_digest(tmp_path)
+
+
+def test_anyio_frozen_execution_keeps_source_and_authority_separate(
+ tmp_path: Path,
+) -> None:
+ source = tmp_path / "test_case.py"
+ source.write_text("def test_ok():\n assert True\n")
+ payload = capture_source_tree(tmp_path)
+
+ async def execute():
+ return await run_pytest_frozen_async(
+ Path(sys.executable), payload, ("test_case.py",),
+ limiter=anyio.CapacityLimiter(1),
+ )
+
+ observed = anyio.run(execute)
+ assert observed.status == "passed"
+ assert observed.source_digest == "sha256:" + hashlib.sha256(payload).hexdigest()
+ assert observed.source_current(tmp_path)
+
+
+def test_anyio_cancellation_waits_for_bounded_verifier_cleanup(
+ tmp_path: Path,
+) -> None:
+ if os.name != "posix":
+ pytest.skip("process-group termination is qualified on POSIX")
+ marker = tmp_path.parent / "cancelled-verifier-marker"
+ started = tmp_path.parent / "cancelled-verifier-started"
+ (tmp_path / "test_case.py").write_text(
+ "import subprocess, sys, time\n"
+ "from pathlib import Path\n"
+ "def test_child():\n"
+ " subprocess.Popen([sys.executable, '-c', "
+ f"\"import time; from pathlib import Path; time.sleep(2); "
+ f"Path({str(marker)!r}).write_text('orphan')\"])\n"
+ f" Path({str(started)!r}).write_text('started')\n"
+ " time.sleep(5)\n"
+ )
+ payload = capture_source_tree(tmp_path)
+
+ async def observe() -> None:
+ await run_pytest_frozen_async(
+ Path(sys.executable), payload, ("test_case.py",),
+ timeout_seconds=1,
+ )
+
+ async def execute() -> None:
+ async with anyio.create_task_group() as tasks:
+ tasks.start_soon(observe)
+ with anyio.fail_after(3):
+ while not started.exists():
+ await anyio.sleep(0.01)
+ tasks.cancel_scope.cancel()
+
+ anyio.run(execute)
+ time.sleep(2.2)
+ assert not marker.exists()
+
+
+def test_frozen_source_rejects_escaping_selectors_and_malformed_bytes(
+ tmp_path: Path,
+) -> None:
+ (tmp_path / "test_case.py").write_text("def test_ok():\n assert True\n")
+ payload = capture_source_tree(tmp_path)
+ with pytest.raises(ValueError, match="selector"):
+ run_pytest_frozen(Path(sys.executable), payload, ("../test_case.py",))
+ with pytest.raises(ValueError, match="trailing"):
+ run_pytest_frozen(Path(sys.executable), payload + b"forged", ("test_case.py",))
+
+
+def test_frozen_source_orders_paths_by_canonical_bytes(tmp_path: Path) -> None:
+ nested = tmp_path / "a"
+ nested.mkdir()
+ (nested / "test_case.py").write_text("def test_ok():\n assert True\n")
+ (tmp_path / "a.txt").write_text("sibling")
+ payload = capture_source_tree(tmp_path)
+ observed = run_pytest_frozen(Path(sys.executable), payload, ("a/test_case.py",))
+ assert observed.status == "passed"
+
+
+def test_caller_tree_change_during_frozen_run_invalidates_observation(
+ tmp_path: Path,
+) -> None:
+ source = tmp_path / "test_case.py"
+ source.write_text(
+ "from pathlib import Path\n"
+ "def test_mutate_caller():\n"
+ f" Path({str(source)!r}).write_text('changed')\n"
+ )
+ observed = run_pytest(Path(sys.executable), tmp_path, ("test_case.py",))
+ assert observed.status == "source-changed"
+ assert not observed.source_current(tmp_path)
+
+
+def test_source_capture_refuses_symlink(tmp_path: Path) -> None:
+ (tmp_path / "real.py").write_text("x = 1\n")
+ (tmp_path / "alias.py").symlink_to(tmp_path / "real.py")
+ with pytest.raises(ValueError, match="symlink"):
+ capture_source_tree(tmp_path)
+
+
+def test_pytest_observation_rejects_timeout(tmp_path: Path) -> None:
+ (tmp_path / "test_case.py").write_text(
+ "from time import sleep\n"
+ "def test_slow():\n"
+ " sleep(5)\n"
+ )
+ observed = run_pytest(
+ Path(sys.executable), tmp_path, ("test_case.py",), timeout_seconds=1
+ )
+ assert observed.status == "timeout"
+ assert observed.exit_code is None
+ assert not observed.passed_obligation
+
+
+def test_pytest_observation_stops_child_process_on_timeout(tmp_path: Path) -> None:
+ if os.name != "posix":
+ pytest.skip("process-group termination is qualified on POSIX")
+ marker = tmp_path.parent / "orphan-marker"
+ (tmp_path / "test_case.py").write_text(
+ "import subprocess, sys, time\n"
+ "def test_child():\n"
+ " subprocess.Popen([sys.executable, '-c', "
+ f"\"import time; from pathlib import Path; time.sleep(2); "
+ f"Path({str(marker)!r}).write_text('orphan')\"])\n"
+ " time.sleep(5)\n"
+ )
+ observed = run_pytest(
+ Path(sys.executable), tmp_path, ("test_case.py",), timeout_seconds=1
+ )
+ assert observed.status == "timeout"
+ time.sleep(2.2)
+ assert not marker.exists()
+
+
+def test_pytest_observation_caps_output(tmp_path: Path) -> None:
+ (tmp_path / "test_case.py").write_text(
+ "def test_loud():\n print('x' * 500_000)\n"
+ )
+ observed = run_pytest(
+ Path(sys.executable), tmp_path, ("test_case.py",),
+ max_output_bytes=4096,
+ )
+ assert observed.status == "output-exceeded"
+ assert observed.exit_code is None
+ assert len(observed.output) <= 8192
+ assert not observed.passed_obligation
diff --git a/bindings/python/uv.lock b/bindings/python/uv.lock
index dac486f..e655752 100644
--- a/bindings/python/uv.lock
+++ b/bindings/python/uv.lock
@@ -2,6 +2,19 @@ version = 1
revision = 3
requires-python = ">=3.11"
+[[package]]
+name = "anyio"
+version = "4.15.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "idna" },
+ { name = "typing-extensions", marker = "python_full_version < '3.15'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
+]
+
[[package]]
name = "cffi"
version = "2.1.1"
@@ -109,6 +122,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
+[[package]]
+name = "idna"
+version = "3.20"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" },
+]
+
[[package]]
name = "iniconfig"
version = "2.3.0"
@@ -123,6 +145,7 @@ name = "lambda-aitia"
version = "0.1.0"
source = { editable = "." }
dependencies = [
+ { name = "anyio" },
{ name = "cffi" },
]
@@ -134,6 +157,7 @@ test = [
[package.metadata]
requires-dist = [
+ { name = "anyio", specifier = ">=4.14.1" },
{ name = "cffi", specifier = ">=2.0" },
{ name = "pytest", marker = "extra == 'test'", specifier = ">=8.0" },
{ name = "setuptools", marker = "extra == 'test'", specifier = ">=77" },
@@ -200,3 +224,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d3691
wheels = [
{ url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" },
]
+
+[[package]]
+name = "typing-extensions"
+version = "4.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
+]
diff --git a/build.ss b/build.ss
index fb9f466..fbbeffd 100755
--- a/build.ss
+++ b/build.ss
@@ -20,6 +20,7 @@
"modules/formal-methods/interface.ss"
"modules/gitops/interface.ss"
"modules/sdlc/interface.ss"
+ "modules/sdlc/retirement-host.ss"
"modules/sdlc/standards/interface.ss"
"user-interface/compositions/aitia.ss"
"user-interface/profiles/software-engineering/project.ss"
@@ -27,7 +28,7 @@
"user-interface/profiles/gitops/dev.ss"
"user-interface/profiles/gitops/staging.ss"
"user-interface/profiles/gitops/production.ss"
- "user-interface/profiles/sdlc/nasa-7150-2d.ss"))
+ "user-interface/profiles/nasa/sdlc.ss"))
(asp-gerbil-scheme-package-spec!
(lambda-aitia-package @ asp-gerbil-scheme-library-package-prototype)
@@ -37,4 +38,10 @@
'((gxc: "testing-interface")
(gxc: "testing-observer"))))
-(defbuild-script (lambda-aitia-native-spec))
+(cond-expand
+ (darwin
+ ;; Gerbil v0.19's cross-module keyword specialization on Darwin can
+ ;; reference private __% procedures from POO CLOS .ssxi metadata.
+ (defbuild-script (lambda-aitia-native-spec) optimize: #f))
+ (else
+ (defbuild-script (lambda-aitia-native-spec))))
diff --git a/docs/adrs/0004-own-assurance-proofs-in-aitia.org b/docs/adrs/0004-own-assurance-proofs-in-aitia.org
index 7c6c188..efc6acd 100644
--- a/docs/adrs/0004-own-assurance-proofs-in-aitia.org
+++ b/docs/adrs/0004-own-assurance-proofs-in-aitia.org
@@ -7,7 +7,7 @@
:ID: lambda-aitia-adr-0004
:STATUS: accepted
:DATE: 2026-09-17
-:REVISION: r1
+:REVISION: r2
:RFC_ID: lambda-aitia-rfc-0001
:DECISION_MAKERS: tao3k-maintainers
:CONSULTED: poo-flow-proof-maintainers
@@ -22,15 +22,19 @@ become owners of Aitia claim, evidence or invalidation semantics.
* Decision Outcome
-Lambda Aitia owns Lean statements and TLA+ models whose vocabulary is defined by
+Lambda Aitia owns Lean statements and Quint models whose vocabulary is defined by
Aitia, including evidence freshness, no stale authority, invalidation,
counterfactual isolation and the assurance lifecycle. It may consume a pinned,
independently versioned POO Flow proof package for generic composition laws.
POO Flow proof sources are referenced, not copied or silently moved.
-Theorems and TLC receipts remain evidence inputs. Neither grants a current
+Theorems and Quint/TLC receipts remain evidence inputs. Neither grants a current
Decision or Effect authority.
+The 2026-10-10 r2 amendment replaces the maintained TLA+ source and TLAPS
+roadmap lane with Quint. TLC remains the bounded checker behind Quint; generated
+TLA+ for fairness is a temporary checker artifact, not a maintained model.
+
* Consequences
- Good: proof statements evolve with their semantic owner.
diff --git a/docs/adrs/0006-production-python-sdlc-runtime.org b/docs/adrs/0006-production-python-sdlc-runtime.org
index cfd2131..7cea091 100644
--- a/docs/adrs/0006-production-python-sdlc-runtime.org
+++ b/docs/adrs/0006-production-python-sdlc-runtime.org
@@ -1,13 +1,14 @@
# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
-#+title: ADR 0006 — Maintain Python as the production SDLC runtime
+#+title: ADR 0006 — Maintain Aitia Python Runtime as a production project
:PROPERTIES:
:ID: lambda-aitia-adr-0006
:STATUS: accepted
:DATE: 2026-09-18
-:REVISION: r1
+:REVISION: r3
+:UPDATED: 2026-09-24
:RFC_ID: lambda-aitia-rfc-0001
:DECISION_MAKERS: tao3k-maintainers
:CONSULTED: poo-flow-maintainers
@@ -23,12 +24,30 @@ implementations to drift.
* Decision
-=bindings/python= is Lambda Aitia's production Python project. Its public
-runtime covers the complete lifecycle vocabulary:
+=bindings/python= is Lambda Aitia's production Python Runtime serving all its
+capabilities within the complete SDLC: requirements, design, algorithms and
+implementation strategies, testing, delivery, operation and evolution. Its
+first lifecycle-planning API projects this assurance/execution protocol:
-#+begin_example
-change -> invalidate -> plan -> verify -> admit -> decide -> authorize -> effect
-#+end_example
+#+begin_export mermaid
+flowchart TD
+ C["change
identified"] --> I["invalidate
dependent support stale"]
+ I --> P["plan
obligations pending"]
+ P --> V["verify
candidate results produced"]
+ V --> A["admit
evidence accepted within scope"]
+ A --> D["decide
decision current"]
+ D --> U["authorize
bounded effect permitted"]
+ U --> E["effect
execution receipt recorded"]
+ V -->|failed or unresolved| P
+ A -->|relevant premise changes| I
+ U -->|revoked or expired| I
+#+end_export
+
+Node labels describe the states a completed stage would establish. Feedback
+edges describe lifecycle reassessment, not edges returned by the current API.
+The API projects only an inert, ordered eight-stage DAG, not executed
+transitions. Admission, decision, authorization and execution each require
+their own checked receipt.
The responsibilities remain layered:
@@ -38,9 +57,12 @@ The responsibilities remain layered:
- The stable C ABI transfers owned JSON request and receipt bytes.
- Python validates and materializes those receipts for production consumers;
it does not reproduce the semantic algorithms.
+- AnyIO is the runtime-wide asynchronous foundation for structured task
+ lifetimes, cancellation and bounded concurrency across Aitia capabilities.
+ It does not grant Host authority or make an inert plan executable.
-The first admitted runtime operation is =SdlcRuntime.plan()=. It obtains the
-canonical eight-stage DAG from Scheme through ABI revision 2. The plan is
+The first exposed planning operation is =SdlcRuntime.plan()=. It obtains the
+eight-stage assurance DAG from Scheme through ABI revision 2. The plan is
inert and explicitly reports that it neither authorizes release nor proves
runtime execution.
@@ -50,8 +72,12 @@ runtime execution.
- Python API stability is a release concern, not example-code maintenance.
- No stage may be presented as executed until its Scheme operation publishes a
source-bound receipt and the POO Flow runtime actually executes it.
-- The complete lifecycle remains the product boundary even while individual
- executable stages are delivered incrementally.
+- The full SDLC organizes the Python project; the planning API is one capability
+ within it. Executable capabilities are delivered only with owned operations
+ and attributable receipts.
+- Generic temporal/causal trajectories and execution mechanisms are reused
+ from POO Flow. Upstream tools, checkers and assets are qualified at their
+ usable boundaries; MRR integration is deferred beyond this Python stage.
* Confirmation
@@ -60,3 +86,11 @@ runtime execution.
- Python tests construct the typed plan through the bundled native library.
- Wheel verification imports and calls the installed production package
without a Gerbil runtime in the Python process.
+
+* Scope clarification
+
+Revision r3 clarifies that the complete SDLC is the framework and the existing
+eight-stage plan is its assurance/execution protocol. It preserves the Python
+project and dependency ownership decision. Async execution, connected causal
+trajectories and real tool-to-Host admission remain pending gates under
+[[file:../rfcs/0004-sdlc-design-and-causal-trajectories.org][RFC 0004]].
diff --git a/docs/adrs/0008-stable-operation-key-after-lost-ack.org b/docs/adrs/0008-stable-operation-key-after-lost-ack.org
new file mode 100644
index 0000000..5897f13
--- /dev/null
+++ b/docs/adrs/0008-stable-operation-key-after-lost-ack.org
@@ -0,0 +1,125 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: ADR 0008 — Bind retry identity to the logical operation
+
+:PROPERTIES:
+:ID: lambda-aitia-adr-0008
+:STATUS: proposed
+:DATE: 2026-09-24
+:REVISION: r2
+:UPDATED: 2026-09-24
+:RFC_ID: lambda-aitia-rfc-0001
+:DECISION_MAKERS: tao3k-maintainers
+:CONSULTED: poo-flow-maintainers
+:INFORMED: lambda-aitia-consumers
+:END:
+
+* Context and Problem Statement
+
+The original job-executor declaration says that the provider deduplicates
+=tenant/job/operation= keys and guarantees at most one commit *per key*. A
+real-process qualification case exposes the gap: after a provider commits but
+its acknowledgement is lost, a restarted worker can generate a new key. The
+provider correctly deduplicates each key while committing the same logical
+job operation twice. This is a known fault-injection case, not a newly found
+defect in a deployed application.
+
+* Decision Drivers
+
+- Keep a single logical identity across retries and process restarts.
+- Preserve completion after a transient acknowledgement loss.
+- Do not claim exactly-once effects without provider idempotency and durable
+ task-state assumptions.
+- Keep a revised guarantee and its evidence separate from the old declaration.
+
+* Considered Options
+
+1. Keep the per-key guarantee and accept duplicate logical operations.
+2. Turn off retries, avoiding duplicates by sacrificing completion.
+3. Persist or deterministically derive a stable provider key from the logical
+ operation identity and revise the guarantee scope.
+
+* Decision Outcome
+
+Propose option 3. The job ledger owns the logical operation identity and retry
+state. The same tenant/job/operation key reaches an idempotent provider after
+restart. The revised =unique-effect= guarantee is at most one external commit
+per logical job operation across retries and restarts, conditional on the
+provider retaining its deduplication record. A changed guarantee, retry
+variation and ADR identity reopen verification; old evidence is not admitted
+by declaration-level compatibility.
+
+* Consequences
+
+- Good: acknowledgement loss does not create a second effect in the bounded
+ qualification case, and the worker can still complete after restart.
+- Bad: key retention, scoping and provider deduplication are new operational
+ dependencies to qualify before any production assurance claim.
+- Neutral: this proposed ADR neither issues a Host seal nor authorizes an
+ effect. Acceptance requires independent review of the actual provider and
+ persistence mechanism.
+
+* Confirmation
+
+- The Python project's installed-wheel qualification runs two worker processes
+ against a persistent SQLite ledger and a loopback HTTP provider. The buggy
+ key strategy fails with two commits; the stable strategy passes with one.
+- The Scheme design-replacement test treats the changed guarantee and decision
+ as requiring fresh review, with no evidence admission or release authority.
+- Full source-bound Host admission and production provider qualification remain
+ open.
+
+* Historical input and current coverage
+
+Revision r2 retains the proposed decision and adds an applicability and owner
+audit. It does not accept the ADR or certify a production provider.
+[[file:../plans/engineering-case-source-inventory.org][The case-source inventory]] records the AWS article used as design inspiration,
+alongside candidate history/observability and composition cases. Its treatment
+of late retries and changed intent motivates questions about retention and
+payload binding; it is not verification evidence for this project.
+
+Source inspection on 2026-09-24 found that the fixture worker uses one literal
+=job-42= key in fixed mode and always sends =commit=. The provider stores the
+effect and key as one SQLite row, uses =insert or ignore= and withholds the
+first ACK. This covers a bounded lost-ACK/restart scenario; it has no separate
+external effect, expiry, parameter-conflict check or tenant namespace. The
+=fixed= mode name denotes the repair to that specific injected fault only.
+The inspected sources are the [[file:../../bindings/python/qualification/job_executor/worker.py][worker]],
+[[file:../../bindings/python/qualification/job_executor/test_unique_effect.py][provider/test fixture]] and [[file:../../modules/assurance/host.ss][Host preflight projection]]. The reused POO Flow boundary is
+=src/modules/authorization/providers/cedar/interface.ss= and its existing
+decision/effect-token projection, not a new Aitia-owned authorization module.
+
+| Contract question | Existing mechanism / evidence boundary | Remaining obligation and owner |
+|-------------------+----------------------------------------+--------------------------------|
+| Is this attempt currently permitted? | POO Flow Cedar Provider and effect-token machinery exist; Aitia projects preflight requests and rechecks their currentness | Host supplies trusted facts; existing Cedar evaluates policy; Runtime enforces at use. The Aitia-to-Runtime path remains unqualified; no new local authorization engine |
+| Does retry preserve logical identity? | fixed mode uses one job identity across two sequential worker processes | Aitia task-state contract and actual provider must qualify tenant/job/operation scope, persistence and concurrent attempts |
+| Can the same identity change its intent? | payload is constant; provider does not compare an incoming payload with the stored payload | provider rejects inconsistent reuse; Host/Runtime bind authorized intent to dispatched bytes. Test mismatch rejection without an additional effect |
+| What if the deduplication record expires? | no expiry or cleanup is modeled | provider owns retention guarantee; Aitia records retry horizon and review conditions. Test boundary/late requests and explicit reconciliation or rejection outside the guarantee |
+| Is effect commit atomic with deduplication? | one SQLite row stands for both key and effect; no failure between independent commits is modeled | actual provider owns atomicity or its recovery protocol. Qualify crashes before/after commit and lost response; policy permission alone is insufficient |
+| What if two attempts are allowed concurrently? | existing scenario restarts workers sequentially | Runtime/provider qualify atomic reservation or deduplication and outcome reconciliation; two Cedar permits must not be mistaken for unique execution |
+
+Do not copy these checks into a second Python policy layer. Trusted
+identity/intent, current authority and applicable retry-window conditions may
+be inputs to the existing Cedar policy boundary. Their truth must come from
+the responsible Host/provider, not caller-supplied booleans. Policy evaluation
+does not persist a deduplication record, make a network request atomic, or
+prove whether an unacknowledged attempt committed.
+
+Retry identity and authorization identity are distinct: a logical operation
+keeps its identity after restart, but an old permit need not remain valid.
+An unknown result must retain that uncertainty until the provider's qualified
+retry/reconciliation contract resolves it. Generic POO Flow reconciliation
+cannot be assumed to establish uniqueness of an external provider effect.
+
+The desired design remains stable logical identity with current authorization
+and a qualified provider contract. The added obligations refine its conditions;
+they do not silently turn the guarantee into unconditional exactly-once delivery.
+
+* More Information
+
+- [[file:../../bindings/python/qualification/job_executor/README.org][Job-executor qualification case]]
+- [[file:../rfcs/0001-causal-assurance-core.org][RFC 0001 — Causal Assurance Core]]
+- [[file:../rfcs/0004-sdlc-design-and-causal-trajectories.org][RFC 0004 — Historical cases and lifecycle design]]
+- [[https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/][AWS Builders' Library — Idempotent API design]]
+- [[https://docs.cedarpolicy.com/auth/authorization.html][Cedar authorization semantics]]
diff --git a/docs/adrs/README.org b/docs/adrs/README.org
index ed73d1a..322d260 100644
--- a/docs/adrs/README.org
+++ b/docs/adrs/README.org
@@ -65,8 +65,9 @@ assurance closure and authority receipt is required before any effect.
- [[file:0003-review-poo-flow-contract-allowlist.org][ADR 0003 — Superseded POO Flow contract allowlist]]
- [[file:0004-own-assurance-proofs-in-aitia.org][ADR 0004 — Own assurance proofs in Aitia]]
- [[file:0005-canonical-snapshot-persistence.org][ADR 0005 — Canonical snapshot persistence]]
-- [[file:0006-production-python-sdlc-runtime.org][ADR 0006 — Maintain Python as the production SDLC runtime]]
+- [[file:0006-production-python-sdlc-runtime.org][ADR 0006 — Maintain Aitia Python Runtime as a production project]]
- [[file:0007-consume-poo-flow-public-library.org][ADR 0007 — Consume POO Flow as the mechanism library]]
+- [[file:0008-stable-operation-key-after-lost-ack.org][ADR 0008 — Bind retry identity to the logical operation (proposed)]]
* Standardization Sources
diff --git a/docs/plans/engineering-case-source-inventory.org b/docs/plans/engineering-case-source-inventory.org
new file mode 100644
index 0000000..01a1d3b
--- /dev/null
+++ b/docs/plans/engineering-case-source-inventory.org
@@ -0,0 +1,77 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Engineering case sources and transfer judgments
+#+startup: overview
+
+:PROPERTIES:
+:ID: lambda-aitia-engineering-case-source-inventory
+:STATUS: draft
+:DATE: 2026-09-24
+:END:
+
+* Scope
+
+This is a reviewed reading inventory, not an imported dataset or a qualified
+Recipe catalog. The links below were inspected on 2026-09-24. No upstream
+source bytes, executable revision or license grant are pinned by this document.
+No article or Issue is reproduced here. Asset reuse requires a separate version,
+attribution/license and execution audit under
+[[file:../rfcs/0004-sdlc-design-and-causal-trajectories.org][RFC 0004]].
+
+* Source claims and our proposed transfer
+
+| Source | Source scope | Aitia design question / proposed transfer | Status and limit |
+|--------+--------------+------------------------------------------+------------------|
+| [[https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/][Malcolm Featonby: Making retries safe with idempotent APIs]]; sections on late requests and changed intent | design account of request identity, retention and parameter mismatch | which component owns identity, trusted retry context and durable effect uniqueness? | inspiration for ADR 0008; no qualification of AWS services or our provider |
+| [[https://github.com/etcd-io/etcd/issues/19179][etcd #19179]]; original report by serathius, 2025-01-13 | reports a resumable-watch violation involving deletion, compaction, crash and resumed watch; original author had no local reproduction | does our assessment distinguish an absent event from incomplete history, and an author's interpretation from a checked cause? | external report only; linked CI artifacts, later resolution and affected-version range not qualified here |
+| [[https://github.blog/news-insights/company-news/oct21-post-incident-analysis/][GitHub: October 21 post-incident analysis]]; Jason Warner, incident in 2018 | describes failover into a database topology incompatible with application expectations | do composition obligations cover recovery topology and latency assumptions across owners? | historical account for applicability review, not a replayed Aitia case or claim about present GitHub architecture |
+
+The last two rows are candidate questions, not findings about Aitia. They are
+not additional implementation workstreams in this slice. Search results,
+Issue closure or repeated citations cannot promote their status.
+
+* Retirement design challenges (2026-09-24)
+
+| Primary source | Observation status | Design transfer and executable check | Remaining limit |
+|----------------+--------------------+--------------------------------------+-----------------|
+| [[https://github.com/temporalio/sdk-python#asyncio-cancellation][Temporal Python SDK cancellation documentation]] | documented behavior, not a reported bug: cancellation can be caught or shielded; cancelling a child-start task is not equivalent to cancelling an already started child | =WorkLifecycle= keeps request separate from completion; =RetirementReviewCancellation= must produce a counterexample | no Temporal integration or reproduction claimed |
+| [[https://github.com/etcd-io/etcd/issues/19179][etcd #19179]] | original report links CI evidence; local reproduction and affected versions are not qualified here | discovered history/inventory gaps reopen completeness; =RetirementReviewUnknown= must fail | the Boolean inventory abstraction is not a model or reproduction of etcd compaction |
+| [[https://github.com/langchain-ai/langgraph/issues/8039][LangGraph #8039]] | author reports checkpoint/write ordering and repeated side effects on recovery, with a reproducer; not independently reproduced here | challenge the assumption that a checkpoint proves all effects are recorded; retain operation identity and replay-epoch obligations | no persistence implementation added; crash/replay model and upstream qualification remain pending |
+
+The stale-review mutant adds a separate Aitia design challenge: work observed
+after review must be rechecked at the final cut. It is not presented as the
+root cause of any of these upstream reports. Source reports motivate tests;
+their conclusions are not admitted as local evidence. See the
+[[file:../receipts/2026-09-24-retirement-formal-review.org][formal review receipt]] for what was actually executed.
+
+* First bounded transfer judgment
+
+The current job-executor shares the first source's response-loss uncertainty
+and logical retry identity question. It differs in workload and provider: one
+fixed job, constant payload, local SQLite and a loopback HTTP endpoint. It has
+no multi-tenant identity, retention policy or independently committed remote
+side effect. Those differences prevent a claim that it implements the source's
+complete service contract.
+
+The source motivates questions; source inspection answers what the fixture
+currently models. [[file:../adrs/0008-stable-operation-key-after-lost-ack.org][ADR 0008]] assigns pending checks to their actual owners,
+including existing Cedar authorization and Runtime handoff mechanisms. The
+judgment is provisional: provider changes, conflicting observations or source
+corrections reopen it. A future executable assessment must bind the consulted
+source revision and affected ADR/claim identities before testing invalidation.
+
+* Acceptance evidence still required
+
+- [ ] Bind the selected source snapshot/claims and accountable applicability
+ review without conflating external reports with local observations.
+- [ ] Record which previously omitted assumption or design option changed,
+ compared with the same design review without the historical source.
+- [ ] Qualify the pending ADR 0008 contracts with actual positive/negative
+ receipts; preserve Cedar, Runtime and provider ownership.
+- [ ] Introduce a corrected source claim and show a new review cut and reopened
+ obligations while preserving the earlier judgment and its evidence.
+
+This inventory adds neither a new public object family nor an inference engine.
+Successful designs and rejected alternatives should be included when selecting
+further cases, so the corpus does not reduce to incident-driven prescriptions.
diff --git a/docs/plans/software-sdlc-integration-inventory.org b/docs/plans/software-sdlc-integration-inventory.org
index fedddda..78a91b8 100644
--- a/docs/plans/software-sdlc-integration-inventory.org
+++ b/docs/plans/software-sdlc-integration-inventory.org
@@ -2,10 +2,15 @@
# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
#+title: Software/SDLC-to-Aitia Integration Inventory
-#+status: draft
-#+date: 2026-09-17
#+startup: overview
+:PROPERTIES:
+:ID: lambda-aitia-software-sdlc-integration-inventory
+:STATUS: draft
+:DATE: 2026-09-17
+:UPDATED: 2026-09-24
+:END:
+
* Purpose
Lambda Aitia owns causal assurance for software engineering and the SDLC,
@@ -17,12 +22,12 @@ ontology surfaces.
The software Architecture Decision Record module is also Aitia-owned and is
published as =ADR=. No lowercase compatibility module is retained.
-The Aitia-side source migration and parity inventory are complete. Publication
-cutover remains evidence-partial until Lambda Episteme publishes the deletion
-of ADR, GitOps and SDLC and POO Flow pins that clean child commit. This
-inventory governs the intended post-migration boundary and prevents either
-repository from reintroducing compatibility aliases or duplicate semantic
-owners.
+The Aitia-side source migration and parity inventory are complete. The
+Episteme deletion is published, and the POO Flow main gitlink already pins a
+clean Episteme cut. The Aitia PR head is newer than the POO Flow main gitlink
+and requires a separate pin update after acceptance. This inventory governs
+the post-migration boundary and prevents either repository from reintroducing
+compatibility aliases or duplicate semantic owners.
Classification:
@@ -34,25 +39,26 @@ Classification:
* Normative layering
-#+begin_example
-Lambda Aitia
- Software Engineering and SDLC modules
- NASA standards and GitOps Profiles
- claim and assumption support
- typed provenance/dependency/causal/assurance/authority relations
- change impact, evidence invalidation and obligation planning
- |
- | inert plans, decisions and receipts
- v
-POO Flow
- POO composition, functional execution, graph substrate,
- verifier/runtime adapters and effect handoff
-#+end_example
+#+begin_export mermaid
+flowchart TD
+ A["Lambda Aitia
Software Engineering, SDLC, NASA standards and GitOps"]
+ A --> S["Aitia assurance semantics
claims, assumptions and typed relations"]
+ S --> I["Impact, invalidation and obligation planning
inert plans and receipts"]
+ I -->|consumes public mechanisms| P["POO Flow
composition, graph, temporal / causal algorithms and Runtime"]
+ A --> Py["Aitia Python Runtime
tools and observations"]
+ Py -->|owned runtime boundaries| P
+ Py -->|candidate evidence, not authority| S
+#+end_export
Lambda Episteme composes beside Aitia when an application needs non-SDLC
vertical knowledge; it is not an upstream owner for Aitia's SDLC semantics.
POO Flow execution does not redefine Aitia reasoning.
+The complete SDLC organizes requirements, design, algorithm/implementation
+strategies, testing, delivery, operation and evolution. RFC 0004 specifies how
+behavior and design/assurance trajectories connect these activities. The
+eight-step assurance/execution protocol is one mechanism within this framework.
+
* Inventory
| Current source | Current owner | Classification | Aitia use | Boundary |
@@ -66,14 +72,37 @@ POO Flow execution does not redefine Aitia reasoning.
| =modules/gitops/=, GitOps/GitHub Profiles and scenario adapter | Lambda Aitia | migrated | repository revision, check and delivery-decision facts | stable public namespace has no version suffix; revision is evidence metadata |
| =modules/decision-kind/= | Lambda Episteme | keep, reference | accountable software decision kinds and conflicts | Aitia links decisions but does not redefine their domain meaning |
| =src/graph/= | POO Flow | reference | graph substrate and typed traversal | Aitia creates no second graph engine |
+| =src/modules/temporal-causality/= | POO Flow | reference | event graphs, cuts, trajectory contracts and temporal classification | Aitia supplies software meaning; complete behavior/design trajectory integration remains pending |
| POO Flow verification contribution contracts | POO Flow | reference | verifier capability and receipt handoff | a plan never invokes a verifier directly |
| =bindings/runtime-c/include/poo_flow/scheme_native_v1.h= pattern | POO Flow | reference | owned-byte AOT Scheme ABI convention | Aitia uses stable unversioned language namespaces; ABI revision is descriptor metadata |
-| =bindings/python= | Lambda Aitia | new | Aitia-owned CFFI consumer and wheel for the Scheme-native ABI | Python transports JSON and never duplicates Scheme decisions |
+| =bindings/python= | Lambda Aitia | maintained | production Runtime across Aitia's SDLC capabilities, CFFI, tool observations and AnyIO dependency | async execution and tool-to-Host admission remain pending; Scheme retains semantic ownership |
| =packages/python-runtime= | POO Flow | keep, separate | generic runtime and lifecycle binding | never owns Aitia or SDLC semantics |
| =packages/proof/lean/PooFlowProof/Enterprise/EvidenceFreshness.lean= | POO Flow proof package | reference | seed for general freshness/no-stale-authority laws | theorem ownership decision must close before any move |
-| SDLC/change lifecycle TLA+ model to be added | Aitia | new | invalidate, plan, reverify, decide and gate lifecycle | must be generic, with NASA supplied only as external facts |
+| =proof/quint/AssuranceLifecycle.qnt= | Aitia | bounded candidate | invalidate, plan, reverify, admit, decide, authorize and effect lifecycle | generic model; stable-environment progress is conditional, and Host enforcement remains open |
| Cedar adapter and Runtime Host | POO Flow | reference | current release/effect authority and final execution gate | no fallback or locally manufactured authority |
+* Candidate reuse qualification
+
+Historical design sources have a separate
+[[file:engineering-case-source-inventory.org][source and applicability inventory]]. Inspiration and judgment do not require
+an executable asset, and do not qualify a Recipe or establish local evidence.
+ADR 0008 records the first source-inspected coverage and mechanism-owner audit.
+
+The following are candidates, not installed or qualified dependencies. Before
+selection, bind exact files and revision/digests, original attribution/license,
+dependencies, applicability assumptions, mandatory checks and replay receipt.
+
+| Candidate | Reuse target | Qualification required | Current status |
+|-----------+--------------+------------------------+----------------|
+| Specula tools and Case Studies | models, harnesses, traces, reproduction programs and applicable checker code | select an asset-complete record; audit attribution, version and replay conditions | unqualified; no archive-wide import or complete-app backend requirement |
+| MRR | future relation queries, derivation and lineage | a later ownership and snapshot-admission contract against its then-current capabilities | deferred; no current Python integration |
+
+The production code follows upstream reuse only after qualification. No new
+proof scorer, Agent kernel or Specula workflow state machine is authorized by
+this inventory. Use existing POO composition and POO Flow runtime mechanisms
+to organize the selected operations. See
+[[file:../rfcs/0004-sdlc-design-and-causal-trajectories.org][RFC 0004]] and the [[file:../../ROADMAP.org][current work queue]].
+
* Existing semantics to preserve
The current SDLC implementation already enforces boundaries Aitia must not
@@ -109,19 +138,19 @@ weaken:
The first fixture is purely software engineering:
-#+begin_example
-Requirement R@1 constrains Implementation I@1
-Verification V@1 checks I@1 against R@1
-Evidence E@1 discharges Obligation O@1 for ReleaseClaim C@1
-Decision D@1 permits deployment Effect X under Snapshot G@1
-
-Change R@1 -> R@2 or I@1 -> I@2
- => dependent assumptions and C@1 impacted
- => E@1 stale for the changed closure
- => O@2 derived for the exact impacted verification set
- => D@1 invalid
- => X blocked
-#+end_example
+#+begin_export mermaid
+flowchart TD
+ R["Requirement R@1
scoped"] -->|constrains| I["Implementation I@1
candidate"]
+ I --> V["Verification V@1
checks I@1 against R@1"]
+ V -->|bound admission| E["Evidence E@1
current"]
+ E --> O["Obligation O@1
discharged for ReleaseClaim C@1"]
+ O --> D["Decision D@1
current under Snapshot G@1"]
+ D --> X["Deployment Effect X
requires separate authorization / Runtime gate"]
+ C["R@2 or I@2
changed"] --> A["Dependent assumptions and C@1
impacted"]
+ A --> S["E@1
stale for changed closure"]
+ S --> N["O@2
pending for impacted verification set"]
+ S --> B["D@1 invalid
X blocked"]
+#+end_export
Required negative cases:
diff --git a/docs/receipts/2026-09-23-phase3-planning-replay.org b/docs/receipts/2026-09-23-phase3-planning-replay.org
new file mode 100644
index 0000000..8edd1e3
--- /dev/null
+++ b/docs/receipts/2026-09-23-phase3-planning-replay.org
@@ -0,0 +1,53 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Phase 3 pure planning replay receipt
+#+startup: overview
+
+:PROPERTIES:
+:ID: lambda-aitia-phase3-planning-replay-2026-09-23
+:STATUS: admitted
+:DATE: 2026-09-23
+:RFC: lambda-aitia-rfc-0001
+:END:
+
+* Exact source cut
+
+- POO Flow mechanism library: =8aa14eafe87ed63d32295f3eac5e5a4eda6658f5=
+- Lambda Aitia: =6b1476b38888080c03e514bc36a6d5789056ac64=
+- Aitia PR CI: =35847725375= (license, Lean, Scheme/integration/Python passed)
+- Local host: Darwin arm64, Gerbil v0.19, =GERBIL_BUILD_CORES=12=,
+ child-process =env -u SDKROOT=.
+
+The source cut is an open-PR candidate, not a merged release. The local replay
+used isolated checkouts rather than the dirty shared parent worktree. Its
+=just check= completed, including the native C ABI harness and Python wheel.
+
+* Source and negative-contract map
+
+The public planner in =modules/assurance/funs.ss= consumes a typed snapshot,
+changed identities and a matching POO-native verification policy. It uses the
+POO Flow Graph dependency order and loop analysis; the plan and verifier-choice
+receipts cannot claim verifier execution, release authority or Runtime work.
+
+=t/assurance/unit/assurance-kernel-test.ss= directly checks:
+
+- deterministic plan and choice digests under inventory permutation;
+- affected prerequisite order, unplanned dependencies, blocked propagation,
+ SCC cycle witnesses and retention of an independent acyclic branch;
+- missing capability, unresolved input and conflicted snapshot blockers;
+- same-identity revision/content changes rejecting old obligation bindings;
+- independent composition obligations and explicit replacement declaration;
+- chosen and rejected verifier candidates, including unavailable capability.
+
+The verification policy has no suppression or authorization operation. It can
+declare capabilities but cannot convert a current failed or blocked request into
+an authority decision; =release-authorized?= and =runtime-executed?= are fixed
+false in the plan model. Authority remains a separate relation and Host gate.
+
+* Admission decision
+
+Phase 3's pure planning and explanation contract is closed for this exact
+source cut. This receipt does not attest that any candidate adapter is installed
+or trusted, that a verifier ran, that evidence was admitted, or that an effect
+was authorized or executed. Those remain Phase 4 and Phase 6 exit gates.
diff --git a/docs/receipts/2026-09-23-phase4-admission-ledger.org b/docs/receipts/2026-09-23-phase4-admission-ledger.org
new file mode 100644
index 0000000..e0fe9f6
--- /dev/null
+++ b/docs/receipts/2026-09-23-phase4-admission-ledger.org
@@ -0,0 +1,52 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Phase 4 Host admission ledger candidate
+#+startup: overview
+
+:PROPERTIES:
+:ID: lambda-aitia-phase4-admission-ledger-2026-09-23
+:STATUS: evidence-partial
+:DATE: 2026-09-23
+:RFC: lambda-aitia-rfc-0001
+:END:
+
+* Source cut
+
+- Aitia base: =9ea6385de909bcde784132532e1e3db7a99a53e4=.
+- POO Flow verification owner: =90d1c79f474f89a4f975b282611de55662301b74=.
+- Isolated Darwin arm64 checkouts; the Aitia load path selected that exact
+ POO Flow source before the older installed library. Child tests retained
+ the runner's =-:max-heap=512M= limit; no global environment was modified.
+
+* Evidence and negative contracts
+
+=assurance-host-admit= reads the Host-configured current snapshot and clock,
+checks candidate evidence and an exactly bound successful outcome, executes
+the configured POO Flow verifier, then issues a private-indexed POO admission.
+It does not alter =AssuranceEvidence.admission-state= or the source snapshot.
+Currentness rechecks the full semantic snapshot, Host epoch, verifier seal,
+candidate evidence, outcome binding and lease. The Host also reconstructs each
+snapshot through =assurance-snapshot= before use, rejecting a POO presentation
+whose displayed digest, context, state or conflict fields disagree with the
+canonical inventory.
+
+=t/assurance/unit/evidence-admission-test.ss= checks that copied or fabricated
+admissions fail; changed source, same displayed digest with changed semantic
+content, invalid snapshot source, expiry, clock rollback, explicit revocation
+and verifier refusal cannot provide current admission. A source cut returning
+to an older value does not revive its earlier admission or Host seal.
+
+Local =just build-scheme= and =just check-semantic= passed, including the
+assurance, ADR, SDLC, GitOps and composition test groups. POO Flow's focused
+verification test passed with its weak-key issued-receipt registry and a GC
+case proving a live indexed admission retains its receipt.
+
+* Limits
+
+This is Host-local issuance and validity, not proof that a deployment installed
+a trusted verifier, clock or snapshot source. It is not yet consumed by an
+assurance Decision, Cedar projection or Runtime effect gate. The older
+snapshot-node =admission-state= remains metadata; no release authority follows
+from a Host admission object alone. CI for this candidate cut is separate
+from the local replay and must be reported independently.
diff --git a/docs/receipts/2026-09-23-phase4-host-capability.org b/docs/receipts/2026-09-23-phase4-host-capability.org
new file mode 100644
index 0000000..21f36c7
--- /dev/null
+++ b/docs/receipts/2026-09-23-phase4-host-capability.org
@@ -0,0 +1,45 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Phase 4 configured verification Host candidate
+#+startup: overview
+
+:PROPERTIES:
+:ID: lambda-aitia-phase4-host-capability-2026-09-23
+:STATUS: evidence-partial
+:DATE: 2026-09-23
+:RFC: lambda-aitia-rfc-0001
+:END:
+
+* Source cut and boundary
+
+- Aitia base: =742291f3c0bbef5df8a7e6c3fcaba93c0c8afe8c=.
+- POO Flow verification mechanism: =8aa14eafe87ed63d32295f3eac5e5a4eda6658f5=.
+- The application-configured =assurance-verification-host= keeps the exact
+ POO Flow adapter, clock and lease in private weak-key storage. Its POO
+ presentation cannot be copied or edited into a capability.
+- Verification refuses an ineligible outcome or evidence not bound to the
+ current snapshot/outcome before invoking the configured operation. The
+ operation must still return true to receive a seal.
+- Support checks read the Host clock internally; expiry, receipt revocation
+ and clock rollback fail closed. The low-level under-adapter function is
+ explicitly conditional, not a Host admission decision.
+
+* Replay
+
+The isolated local checkout used the current POO Flow source before the older
+installed library in =GERBIL_LOADPATH=. The first attempted test with only the
+older installed POO Flow failed an existing fake-adapter rejection assertion;
+that run is not accepted as evidence for this source cut. With the pinned
+source in the load path, the atomic evidence-admission and assurance-kernel
+tests passed, as did =just build-scheme=, the complete assurance test group,
+and =just check-semantic= across assurance, ADR, SDLC, GitOps and modules.
+The test runner capped each child at =-:max-heap=512M=.
+
+* Limits
+
+The Host constructor is a configuration capability, not proof that a trusted
+deployment installed it or that its clock is secure. =AssuranceEvidence='s
+caller-supplied =admission-state= is still metadata; there is no Host-owned
+promotion transition, Cedar projection, or Runtime effect gate in this slice.
+No seal or green test outcome is release authority.
diff --git a/docs/receipts/2026-09-23-phase5-lifecycle-model.org b/docs/receipts/2026-09-23-phase5-lifecycle-model.org
new file mode 100644
index 0000000..8718e0b
--- /dev/null
+++ b/docs/receipts/2026-09-23-phase5-lifecycle-model.org
@@ -0,0 +1,60 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Phase 5 bounded lifecycle model replay receipt
+#+startup: overview
+
+:PROPERTIES:
+:ID: lambda-aitia-phase5-lifecycle-model-2026-09-23
+:STATUS: evidence-partial
+:DATE: 2026-09-23
+:RFC: lambda-aitia-rfc-0001
+:END:
+
+This is a historical TLA+ receipt. Its =proof/tla/= paths refer to the source
+at commit =49cbc5d5fd4ec983c98dca8070a0b99bc3fc68b4=; the maintained model
+and checks are now in [[file:../../proof/quint/README.org][Quint]].
+
+* Exact model cut
+
+- Aitia base commit: =608525c84e853ed5f5b156693c8f72fe58cfcf97=;
+ the progress extension and this updated
+ receipt are the candidate change on top of that commit.
+- =AssuranceLifecycle.tla= SHA-256:
+ =a2a12f2fe4723fe76a248a1eaeebdfd8130590e150bda82116f854f179dde9ab=.
+- Safe TLC configuration SHA-256:
+ =984c59bc517d3ae0a4477e7cb578f4a5a553bd966aed577d202e513679df21e9=.
+- Progress and no-fairness configuration SHA-256:
+ =4940784d1a627de1642cef9f4df7714780345a66aa70b1421923e360558db21f= and
+ =67ed10ce6eb65ef8eb2998f8f3bfce0cb0984fea1747fd9cd867b15fb31de439=.
+- TLC2 2.19, single worker, =JAVA_TOOL_OPTIONS=-Xmx512m=, Darwin arm64.
+- Replay commands and the expected invariant for every configuration are in
+ =proof/tla/README.org=. Each run used a separate =-metadir= under
+ =/private/tmp=; no global Gerbil/Nix environment was changed.
+
+* Result
+
+| Configuration | Expected | Observed |
+|---------------+----------+----------|
+| Safe | exit 0 | 7,468 generated; 2,202 distinct; queue empty |
+| Stable progress with weak fairness | exit 0 | 38 generated; 18 distinct; temporal property passed |
+| Stable progress without fairness | =EventuallyEffect= counterexample | exit 12; stuttering after authorization |
+| Reachable | =NoEffectStarted= counterexample | exit 12; valid effect trace |
+| Unsafe report-only effect | =NoUnsafeEffectStart= counterexample | exit 12 |
+| Late admission after change | =AdmissionIsCurrent= counterexample | exit 12 |
+| Grant before re-verification | =GrantRequiresCurrentDecision= counterexample | exit 12 |
+| Effect after revocation | =NoUnsafeEffectStart= counterexample | exit 12 |
+
+These are local bounded-model results, not CI results. A deliberate mutant's
+exit 12 is a successful *negative contract* only when TLC names the expected
+invariant. The safe run's queue was empty, so the finite state space for the
+declared bounds was exhausted.
+
+* Admission boundary
+
+This receipt supports a candidate dynamic lifecycle contract. It does not
+promote a reported result to admitted evidence, establish a trusted Host
+adapter/clock, cryptographically bind a Runtime grant, or close Phase 5.
+Unconditional progress, counterfactual transition authority, source-bound CI
+replay, and the executable Host handoff remain open. The existing POO Flow Cedar
+=governance_admitted= field is not an Aitia evidence-admission decision.
diff --git a/docs/receipts/2026-09-24-formal-source-qualification.org b/docs/receipts/2026-09-24-formal-source-qualification.org
new file mode 100644
index 0000000..55e126d
--- /dev/null
+++ b/docs/receipts/2026-09-24-formal-source-qualification.org
@@ -0,0 +1,124 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Formal source qualification — local joint gate
+#+startup: overview
+
+:PROPERTIES:
+:ID: lambda-aitia-formal-source-qualification-2026-09-24
+:STATUS: locally-qualified-source-boundary
+:DATE: 2026-09-24
+:RFC_ID: lambda-aitia-rfc-0004
+:END:
+
+This is a historical TLA+ receipt. Its =proof/tla/= paths refer to the source
+at commit =49cbc5d5fd4ec983c98dca8070a0b99bc3fc68b4=; the maintained model
+and checks are now in [[file:../../proof/quint/README.org][Quint]].
+
+* What closes here
+
+This follows the [[file:2026-09-24-retirement-ingress-review.org][ingress review]]. The previously rejected multiline and modular
+TLA+ forms now parse in the parser owner without changing the Aitia models.
+All five maintained models pass syntax success, artifact validity and exact
+source-byte roundtrip. TLC and Lean remain independent gates.
+
+The native repair lives in the existing linked gerbil-parser checkout, not a
+new Aitia parser. Its grammar now treats newlines as lossless whitespace rather
+than declaration terminators. Named instances, qualified names and flat leading
+junction expressions have explicit CST nodes. The scope does not include
+nested indentation-sensitive junction lists, full TLA+ syntax, configuration
+parsing or rebuilt Rust/Rowan AOT products. TLC still performs its own parsing
+and semantic checks for the exact models/configurations.
+
+An initial optional-newline production introduced a shift ambiguity at the
+next definition and exceeded a 60-second probe. It was replaced, not accepted
+as qualified. The stale diagnostic process was interrupted. Gerbil probes
+kept a 512 MiB heap; one cold generation diagnostic used a 180-second wall limit.
+The final native regression processes completed within the 60-second gate.
+
+* Maintained POO declaration and source binding
+
+[[file:../../user-interface/scenarios/job-retirement-contract.ss][The retirement contract]] declares identity =job-executor/retirement=,
+revision =r1=, guarantee =retirement-safety/g1= and four explicit assumptions:
+atomic epoch fencing, known inventory, no new grant after closure, and the
+single-client bound. It grants no allowed runtime effects.
+
+[[file:../../t/formal/retirement-binding-test.ss][Binding qualification]] uses existing =sdlc-design-implementation-link=,
+=sdlc-design-implementation-review=, Assurance snapshots and source digests.
+The TLA source group contains four model/helper files and fourteen configs;
+the Lean group contains both retirement proof modules. Every member's content
+digest participates in its source-group identity. The links bind both groups
+to the same contract identity, revision, contract digest and snapshot cut.
+
+Checks confirm that either source-group mutation changes the snapshot context
+and invalidates both old links, even if nominal revisions are unchanged. A
+changed design assumption also rejects both links as =contract-changed=.
+An initial test incorrectly expected =source-changed=; the existing owner
+correctly diagnoses =snapshot-changed= first. The test was corrected without
+weakening that owner or changing invalidation semantics.
+
+These are declared source bindings. They do not prove that the formal formulas
+are faithful projections of the natural-language contract, that a Scheme
+implementation refines the models, or that the Host admitted verifier evidence.
+Review values continue to report =implementation-conforms?=,
+=evidence-admitted?= and =release-authorized?= as false. A source-group manifest
+is not a complete verifier/toolchain attestation. The explicit joint gate runs
+every verifier; source mapping alone is not evidence-coverage admission.
+
+* Executed gates and replay
+
+| Gate | Result |
+|------+--------|
+| parser-owned native suite | 12 test cases, including new CST/roundtrip and incomplete-form rejection tests; =MODULE-OK= / =HARNESS-OK= |
+| Aitia native syntax suite | 2 cases; five actual model files plus a lossless rejected-source control |
+| Aitia POO binding suite | 4 cases; current binding and source/premise invalidation |
+| TLC | 22 configurations match positive/expected-counterexample outcomes |
+| Lean | both libraries build; all 20 audited laws have no axioms and no =sorryAx= |
+| repository hygiene | license contract and its 7 tests pass; both changed repositories pass whitespace checks |
+
+=just check-formal= runs the two native suites, TLC and Lean in order. It is an
+explicit qualification lane while the parser change remains uncommitted;
+default CI is not silently changed to require an unpublished dependency.
+With the current package layout, from the Aitia root:
+
+#+begin_src sh
+GERBIL_PATH="$PWD/../../.gerbil" \
+GERBIL_LOADPATH="$PWD/../../.gerbil/pkg/github.com/tao3k/gerbil-parser:$PWD:$PWD/../..:$PWD/../../.gerbil/lib" \
+TLA_TOOLS_JAR=/path/to/verified/tla2tools.jar just check-formal
+#+end_src
+
+No new root variable, global tool installation or source flattening is needed.
+The existing atomic test recipe bounds its observer at 1 GiB and its native
+test child at 512 MiB; direct parser probes used 512 MiB for the observer too.
+TLC uses its checksum-pinned jar, one worker and 512 MiB Java heap. Its local
+RMI listener required sandbox execution permission. No remote CI run is claimed.
+
+* Source cut
+
+Aitia remains at =de65fa2033ce62569147ee5411c05c50051206dd= with local changes.
+The linked parser branch advanced independently during the work from
+=f42393559de4aa78620a836777bbfd91ee6751ca= to
+=f0df5a11d8014bdfe8be30be5a1f3ad9203ce0bf=. Its unrelated artifact-filename
+change is preserved. Our parser edits remain confined to the two files below.
+The qualified parser source resolves through the parent's existing package
+link to =/private/tmp/gerbil-parser-rowan-scanned-tokens=.
+
+| Source | SHA-256 |
+|--------+---------|
+| parser =languages/tla-plus/v1/grammar.ss= | =a419e55d490afbdd7c8e31cf36fa17b2e598aac0b470dd6d8e2a5a12089299e6= |
+| parser =languages/tla-plus/v1/parser-test.ss= | =44fcd73fd6e9f736ec8b736db0f92fc83f635a0ef04f1bfca16a0874ee5a56da= |
+| Aitia =user-interface/scenarios/job-retirement-contract.ss= | =e333a91bf0964fb2762ddcf54e7bd5fa7c5e8071e721a45b50479617c27b5965= |
+| Aitia =t/formal/tla-syntax-test.ss= | =2f95897254ac97067eba5f728fd01be2b3b7f49ac1f2be2aef8fe1c25a8ff982= |
+| Aitia =t/formal/retirement-binding-test.ss= | =93438d52f8e71242615b6ef8feeb5f600d83da7a0344de6ad33916f1c3679f79= |
+
+* Remaining exit gates
+
+- Publish the parser-owned repair, update the actual dependency authority and
+ independently replay without relying on this dirty linked checkout. Neither
+ commits, pushes nor dependency-pin changes were performed in this slice.
+- Establish semantic correspondence or generation of the formal projections
+ from the owning POO contract; source identity tests cannot substitute for it.
+- Refine atomic fencing to actual authority/Runtime behavior, then evaluate
+ repeated clients and fairness under explicit assumptions.
+- Keep persistence and actual effect execution deferred. This is local closure
+ of the source/verification plumbing, not production or SDLC-wide closure.
diff --git a/docs/receipts/2026-09-24-retirement-formal-review.org b/docs/receipts/2026-09-24-retirement-formal-review.org
new file mode 100644
index 0000000..8b811da
--- /dev/null
+++ b/docs/receipts/2026-09-24-retirement-formal-review.org
@@ -0,0 +1,162 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Retirement design — modular formal review
+#+startup: overview
+
+:PROPERTIES:
+:ID: lambda-aitia-retirement-formal-review-2026-09-24
+:STATUS: evidence-partial
+:DATE: 2026-09-24
+:RFC_ID: lambda-aitia-rfc-0004
+:END:
+
+This is a historical TLA+ receipt. Its =proof/tla/= paths refer to the source
+at commit =49cbc5d5fd4ec983c98dca8070a0b99bc3fc68b4=; the maintained model
+and checks are now in [[file:../../proof/quint/README.org][Quint]].
+
+* Decision and evidence boundary
+
+New persistence integration is deferred. The prior
+[[file:2026-09-24-retirement-impact.org][Scheme Impact checks]] exercise structural/temporal analysis, not a proof of
+retirement safety. Existing Lean laws covered fixed-snapshot impact and design
+substitution; the existing TLA+ model covered assurance admission/authorization.
+Neither covered the new retirement review. This slice adds design experiments,
+not deployed deletion, a new public DSL, or a second generic Impact engine.
+
+[[file:../plans/engineering-case-source-inventory.org][The source inventory]] distinguishes Temporal's documented cancellation
+semantics, the etcd watch report and a LangGraph recovery report from our own
+proposed transfers. None was replayed locally in this slice. Known upstream
+reports are not new bug discoveries, and these models do not reproduce their
+implementations.
+
+* Executed checks
+
+Run from the Aitia root with the existing pinned =TLA_TOOLS_JAR=:
+
+#+begin_src sh
+just check-tla work
+just check-tla retirement
+just check-tla
+just check-proof
+just check-license-contract
+#+end_src
+
+| Lane / configuration | Generated / distinct states | Observed result |
+|----------------------+-----------------------------+-----------------|
+| =WorkLifecycle= | 14 / 8 | exit 0; queue empty |
+| =WorkCancellation= | 3 / 2 | exit 0; work remains after request |
+| =WorkCancellationReachable= | 2 / 2 | exit 12; =NeverCancelled= violated |
+| =WorkQuiescenceReachable= | 8 / 7 | exit 12; =NeverQuiescent= violated |
+| =RetirementReview= | 84 / 42 | exit 0; queue empty; deadlock checks enabled |
+| =RetirementReviewCancellation= | 24 / 15 | exit 12; =NoOutstandingWork= violated |
+| =RetirementReviewStale= | 46 / 27 | exit 12; =NoOutstandingWork= violated |
+| =RetirementReviewUnknown= | 49 / 28 | exit 12; =InventoryKnown= violated |
+| =RetirementReviewReachable= | 39 / 21 | exit 12; =NeverRetired= violated |
+
+All nine new configurations and the eight unchanged assurance configurations
+passed their expected-outcome contracts. This means 17 configurations within
+three unittest methods, not 17 successful safety theorems. The harness checks
+the exact counterexample property, exit code, nonempty exploration and trace.
+Negative checks stop at a counterexample and need not exhaust their queues.
+
+One local work-only run took 2.595 s; retirement-only took 3.321 s; the final
+all-module run took 11.390 s. These are single-run unittest wall times including
+JVM startup, not a repeated benchmark or a general speedup claim. Atomic
+selection avoids unrelated models; changing the shared work module still
+requires its retirement consumer check.
+
+Lean 4.31.0 built both assurance/SDLC libraries. Six new exported laws prove
+initial safety, one-step preservation, reachable-state safety and three unsafe
+state witnesses. All 16 audited laws reported no axioms; no =sorryAx= was used.
+The observation relation overapproximates all nonterminal observations, while
+the finalize action checks current work/inventory. This is an abstract-model
+proof, not an established refinement from TLA+ or Scheme.
+
+The invalid module selector =not-a-module= was also checked with the project's
+existing Python interpreter: argparse exited 2 rather than running zero tests.
+The same unittest suite remains discoverable through the existing CI command.
+No remote CI run or clean-checkout validation is claimed.
+
+* Unclosed native syntax gate
+
+The parent dependency resolves to gerbil-parser commit
+=562dcd428f00b05ca87407fd54b1e04dcf076ae9= at
+=/private/tmp/gerbil-parser-rowan-scanned-tokens=, with a clean source worktree
+at inspection. Importing its public =parse-tla-plus-v1= and runtime artifact
+checks under a 512 MiB Gerbil heap / 60-second timeout rejected both sources:
+
+| Source | First diagnostic |
+|--------+------------------|
+| =WorkLifecycle.tla= | =GERBIL-PARSER-TLA-PLUS-V1=, =lr-no-action=, state 209, byte 472: newline after the =RequestCancellation= definition header |
+| =RetirementReview.tla= | same code/kind, state 205, byte 541: newline within the =VARIABLES= declaration |
+
+The inspected grammar requires these expressions/declarations on one line;
+TLC's parser accepts the native multiline forms. These are first-failure
+locations, not an exhaustive parser capability audit: named =INSTANCE= and
+qualified operators also require qualification after the newline gap is fixed.
+No alternate parser, flattened-source workaround or false roundtrip receipt
+was introduced. Native parser acceptance/byte-roundtrip remains failed.
+
+The executable reproduction uses the parent =GERBIL_PATH= and its existing
+=GERBIL_LOADPATH= dependency source/library paths, then for each model:
+
+#+begin_src scheme
+(import (only-in :std/misc/ports read-all-as-string)
+ (only-in :gerbil-parser/languages/tla-plus/v1/parser parse-tla-plus-v1)
+ (only-in :gerbil-parser/src/runtime/artifact
+ parse-artifact-success? parse-artifact-valid?
+ parse-artifact-roundtrip parse-artifact-ref))
+(let* ((source (call-with-input-file "proof/tla/WorkLifecycle.tla" read-all-as-string))
+ (artifact (parse-tla-plus-v1 source)))
+ (displayln (parse-artifact-ref artifact 'diagnostics))
+ (exit (if (and (parse-artifact-success? artifact)
+ (parse-artifact-valid? artifact)
+ (equal? source (parse-artifact-roundtrip artifact))) 0 1)))
+#+end_src
+
+* Reproducibility and source cut
+
+Aitia HEAD is =de65fa2033ce62569147ee5411c05c50051206dd= with uncommitted
+changes; this is a shared-worktree receipt. TLC release v1.7.2 reports TLC2
+2.17 (2022-02-02, revision 3c7caa5); its SHA-256 is
+=fa18543e44ed5974a85bd2c60c0dc16620ae117680ea8e693d2691999ed90b22=.
+Each execution used one worker, =-Xmx512m=, a fresh temporary state directory
+and a 60-second process timeout. The sandbox first blocked Java's local RMI
+listener; the same bounded checks passed with local execution permission.
+
+| Source (relative to Aitia) | SHA-256 |
+|---------------------------+---------|
+| =proof/tla/WorkLifecycle.tla= | =079452f967fc2f1d21a1e962dd0d38a5e62fa6b36a4055d2c42358fb006fbb23= |
+| =proof/tla/RetirementReview.tla= | =4edbe49d59f663a007ad986108d1e374e3408c4593fb4c4c53a34c02232ed01a= |
+| =proof/lean/LambdaAitiaProof/Modules/Sdlc/Retirement.lean= | =86f899c87cfd51ed2daa23dbc0c4b3bb88914851b42afeed28dc51e067eb45d3= |
+| =bindings/python/tests/tla_contract.py= | =788e97254d0923104357654b59e6ad2902a9ec483eb72f35c75d8e18657c13a3= |
+| =proof/tla/WorkCancellation.cfg= | =503131d547fa46de7555b245156dc072ebb04c4a32f3fc6f4672ff600f751198= |
+| =proof/tla/WorkCancellationReachable.cfg= | =a79ba63517e17e4fd7ca1eb7d2964455f8c40e58a2b5a87aef6f29d565d8f5b1= |
+| =proof/tla/WorkLifecycle.cfg= | =1084116ad37aeb2bbb511ce9fd7bded6381c946f0ba39f927116ff722a8910bf= |
+| =proof/tla/WorkQuiescenceReachable.cfg= | =a4467507d11ce289101a29644dd84d45c57fb72c534d25d096b2276d46d31ed7= |
+| =proof/tla/RetirementReview.cfg= | =9a433fe46ec346fe6d180ca0483b415cdf0f6f4fc9650cb739c6ce4e72dfd2df= |
+| =proof/tla/RetirementReviewCancellation.cfg= | =641e05f7356e568494e2dc7fbc340635031706593a07971c612c6891ada31969= |
+| =proof/tla/RetirementReviewReachable.cfg= | =1ec1a31f4e3d25c0d7dba39abdc1f79300a4ef7a80e683565cdf8a2d96231093= |
+| =proof/tla/RetirementReviewStale.cfg= | =28e8245f7bb71691d7d0b274a06b6458d6a81f932520b74bc5d228f6d9e8442f= |
+| =proof/tla/RetirementReviewUnknown.cfg= | =e935c67af6280fb68fddb1819ed0364ddbb5cd21aae6fb4f428df48d9027d61a= |
+
+* Next gates, not claimed complete
+
+Follow-up: [[file:2026-09-24-retirement-ingress-review.org][the ingress review]] adds a bounded challenge to item 3 and extracts the
+existing grant predicate. The source hashes and 17-case results above remain
+the earlier cut; the follow-up records its changed runner/model hashes and
+22-case run. Items 1, 2 and live authority correspondence remain open.
+
+1. Qualify the native parser for modular/multiline TLA+ without weakening the
+ source-byte identity or introducing a downstream parsing authority.
+2. Bind admitted Scheme Contract identity/revision and projection digests to
+ the TLA+ and Lean views, with an executable correspondence check.
+3. Challenge the terminal assumption by composing retirement with ingress and
+ revocation: a late request after finalization must not be hidden by a
+ frozen state. Reuse the current authority owners; do not add a policy engine.
+4. State fairness assumptions before proving eventual drain/retirement.
+ Unknown inventory, replay, multiple consumers and repeated late arrivals
+ remain outside this finite model's complete guarantees.
+5. Keep persistence and real effect execution deferred. Neither abstract proof
+ nor the synthetic Scheme case constitutes production readiness.
diff --git a/docs/receipts/2026-09-24-retirement-impact.org b/docs/receipts/2026-09-24-retirement-impact.org
new file mode 100644
index 0000000..e9e147b
--- /dev/null
+++ b/docs/receipts/2026-09-24-retirement-impact.org
@@ -0,0 +1,102 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Job-executor retirement Impact — bounded analysis receipt
+#+startup: overview
+
+:PROPERTIES:
+:ID: lambda-aitia-retirement-impact-2026-09-24
+:STATUS: evidence-partial
+:DATE: 2026-09-24
+:RFC_ID: lambda-aitia-rfc-0004
+:END:
+
+* Subject and source boundary
+
+The selected capability is automatic retry in a synthetic job-executor case.
+The candidate successor is reviewed recovery. These are fixture identities,
+not deployed features or evidence that a migration occurred. The case reuses
+POO Flow Graph and its public Temporal Causality interface; no new traversal,
+time classifier, policy evaluator or Runtime is implemented.
+
+The [[file:../../user-interface/scenarios/job-executor-retirement.ss][maintained scenario]] contains pre-removal and candidate post-removal graphs,
+plus an explicitly identified synthetic event history. The [[file:../../t/sdlc/unit/job-retirement-impact-test.ss][Scheme qualification]]
+checks inert analysis receipts. It does not read the Python fixture's SQLite
+ledger; historical results and current activity here are modeled facts.
+
+The shared worktrees were dirty. The parent checkout reported
+=a9a2b331d42fda409ea142fb67137f395721397e= and Aitia reported
+=de65fa2033ce62569147ee5411c05c50051206dd=; the new files below were uncommitted.
+This is not an isolated clean-build or release receipt.
+
+| Source | SHA-256 |
+|--------+---------|
+| Aitia =user-interface/scenarios/job-executor-retirement.ss= | =1977be963e74ef6aabd48ff7b1649f423b1cb2a9b047d273bbfbf98e42c43044= |
+| Aitia =t/sdlc/unit/job-retirement-impact-test.ss= | =8bc2fab67c2249a1433038f7c957e94189a8d65f1b47067ffc849eefc94887a6= |
+| POO Flow =src/modules/temporal-causality/funs.ss= | =4ffc3517b48bba51abafb28f91c3e0a2e0994a1904911509b222e2780c0df00b= |
+| POO Flow =src/modules/temporal-causality/objects.ss= | =19ada4187ad1f380c7dc95f58be97c24681f01b9d8d0ac21afaa582583893454= |
+| POO Flow =src/modules/temporal-causality/types.ss= | =78499931958750bbfb458c14ba92beb3d44a4d1e6f0298f8c87adf1a85fd6a53= |
+
+These are inspected source hashes, not an attestation of all transitive binary
+dependencies. Tests use the existing compiled dependency library and source
+load paths below. No claim of fresh whole-package compilation is made.
+
+* Seven retirement checks
+
+1. The old graph retains the historical result, active job, queued job and its
+ verification dependency; an unrelated export is excluded. The verification
+ witness is =automatic-retry → queued-job → verify-queued-retry=.
+2. Selecting the removed identity in the candidate graph raises an error
+ instead of reporting no impact; the before graph remains available.
+3. An incomplete external-consumer inventory yields =partial-impact=.
+4. The cut at logical position 4 separates past completion/start, explicit
+ current activity, declared future retry, counterfactual retirement and an
+ out-of-horizon retry. No activity is inferred merely from an old start.
+5. Creating the later cut retains the earlier historical cut and its original
+ observed result/payload.
+6. A missing causal parent remains =unknown-consumer-origin= in the unknown
+ frontier and yields partial temporal classification.
+7. The retirement proposal cannot be selected as an observed trigger in the
+ current cut. Neither structural nor temporal analysis grants execution.
+
+* Local replay
+
+From the Aitia root, against the existing parent dependency installation:
+
+#+begin_src sh
+GERBIL_LOADPATH="$PWD:$PWD/../..:$PWD/../../.gerbil/lib" \
+ just test-scheme-atomic sdlc unit/job-retirement-impact-test.ss
+GERBIL_LOADPATH="$PWD:$PWD/../..:$PWD/../../.gerbil/lib" \
+ just test-scheme-atomic sdlc unit/design-contract-test.ss
+env -u SDKROOT GERBIL_PATH="$PWD/.gerbil" \
+ GERBIL_LOADPATH="$PWD:$PWD/../..:$PWD/../../.gerbil/lib" \
+ timeout --foreground --signal=TERM --kill-after=3s 60s \
+ gxi -:max-heap=512M ./run-test.ss ../../t/temporal-causality-core-test.ss
+#+end_src
+
+All three focused modules returned =MODULE-OK= and =HARNESS-OK= with exit 0.
+They contain seven retirement, eight design-replacement and eight core cases.
+Gerbil reported =2591dcd on Gambit dcd677c=. The existing Testing Interface
+selects a 512 MiB isolated test process; the existing Justfile's observer
+process has a separate 1 GiB bound. The direct core observer used 512 MiB.
+
+The initial run with only the child default load path failed to resolve
+=asp-gerbil-scheme/testing-api=. Supplying the parent dependency library via
+=GERBIL_LOADPATH= fixed module discovery; no new root variable, global package
+installation or user-environment mutation was introduced.
+
+* Remaining closure
+
+- [ ] Derive current activity and historical results from actual persisted
+ task/provider observations, rather than this synthetic inventory.
+- [ ] Bind a source-level change and both graph revisions to admitted SDLC
+ facts, then derive reviewed migration/drain obligations with accountable owners.
+- [ ] Exercise actual cancellation/migration through existing authorization and
+ Runtime boundaries, including races and failure/recovery receipts.
+- [ ] Demonstrate replay/interpretation of an old persisted result and prevent
+ a successor from inheriting incompatible historical support.
+- [ ] Qualify the Scheme-owned Org Contract / Rowan presentation separately;
+ these tests do not exercise Org parsing or the old CLI.
+
+The lifecycle retirement gate in RFC 0004 remains open. This receipt qualifies
+one bounded analysis scenario, not safe production deletion or completed SDLC.
diff --git a/docs/receipts/2026-09-24-retirement-ingress-review.org b/docs/receipts/2026-09-24-retirement-ingress-review.org
new file mode 100644
index 0000000..9b00403
--- /dev/null
+++ b/docs/receipts/2026-09-24-retirement-ingress-review.org
@@ -0,0 +1,147 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Retirement ingress — terminal assumptions challenged
+#+startup: overview
+
+:PROPERTIES:
+:ID: lambda-aitia-retirement-ingress-review-2026-09-24
+:STATUS: evidence-partial
+:DATE: 2026-09-24
+:RFC_ID: lambda-aitia-rfc-0004
+:END:
+
+This is a historical TLA+ receipt. Its =proof/tla/= paths refer to the source
+at commit =49cbc5d5fd4ec983c98dca8070a0b99bc3fc68b4=; the maintained model
+and checks are now in [[file:../../proof/quint/README.org][Quint]].
+
+* Design finding
+
+The [[file:2026-09-24-retirement-formal-review.org][isolated review model]] prevents observations after its terminal state.
+That does not prove that external callers stop sending requests. The new
+[[file:../../proof/tla/RetirementIngress.tla][ingress composition]] permits a request before or after retirement, including
+a client retaining a cached grant. Merely removing a capability from a graph,
+or marking review complete, does not fence these callers.
+
+The modeled correction is ordered: close grant issuance and advance the
+authority epoch, drain known work, then finalize. At effect start, recheck the
+grant epoch. =EffectAdmission.CurrentGrant= is extracted without changing the
+existing =AssuranceLifecycle.StartEffect= predicate; both consumers use that
+same operator. This is reuse of a formal authorization contract, not a new
+policy evaluator or a claim that Cedar/Runtime already implements the mapping.
+
+* Executed model checks
+
+#+begin_src sh
+just check-tla ingress
+just check-tla
+just check-proof
+#+end_src
+
+The same pinned TLC jar and 512 MiB / one-worker / 60-second-per-run limits as
+the earlier receipt were used. Local RMI required sandbox execution permission.
+
+| Configuration | Generated / distinct | Expected and observed outcome |
+|---------------+----------------------+-------------------------------|
+| =RetirementIngress= | 741 / 248 | exit 0, queue empty; no post-retirement start; retired work drained, inventory known, epoch fenced |
+| =RetirementIngressUnfenced= | 413 / 150 | exit 12, =NoPostRetirementStart= violated when finalization omits the fence |
+| =RetirementIngressCachedGrant= | 524 / 179 | exit 12, same invariant violated when effect start skips grant validation |
+| =RetirementIngressRejected= | 499 / 178 | exit 12, =NeverRejectAfterRetirement= violated: a later request really reaches rejection |
+| =RetirementIngressStarted= | 38 / 23 | exit 12, =NeverStarted= violated: the model also allows an ordinary pre-retirement start |
+
+The last two are non-vacuity controls, not defects. The safe model includes
+explicit terminal self-loops and keeps deadlock checking enabled. Negative
+configurations require an actual trace and the named invariant violation, not
+an arbitrary nonzero process result.
+
+All 22 configurations across four unittest methods matched their contracts.
+The eight existing assurance checks retained exactly their preceding generated
+and distinct state counts after extraction of =CurrentGrant=. This is a
+regression observation, not a formal equivalence proof of the extraction.
+The ingress-only run took 3.407 s and the complete run 14.813 s; these are
+single-run wall times including JVM startup, not general performance claims.
+
+Four additional Lean laws prove initial safety, incompatibility of a current
+cached epoch with safe retirement, preservation across abstract boundary steps,
+and reachable-state safety. They reuse =Retirement.stepPreservesSafety=.
+Requests/rejections can stutter after retirement; a current-grant start is not
+excluded by a hard-coded retired guard but by the proved fence invariant.
+All 20 exported audited laws reported no axioms and no =sorryAx=.
+
+* Explicit abstraction limits
+
+- One pending request and one attempt are modeled, with epochs 0 and 1 and a
+ cached epoch 0. Revision, admission, decision and time are fixed to isolate
+ revocation. New grant issuance, grant expiry and repeated retries are not
+ explored by this composition; the existing lifecycle regression is separate.
+- Closing issuance and advancing the epoch is one atomic action. Actual
+ distributed ordering, concurrent authorities and restart behavior remain
+ obligations, not facts inferred from TLC.
+- =known= is an assumption about inventory, not proof of actual completeness.
+ Post-retirement discovery of previously unknown work is not covered.
+- No fairness or eventual retirement is claimed. Lean's abstract relation is
+ not yet a proved refinement of the TLA+ actions or a Scheme implementation.
+- Persistence, runtime effects, package publication and remote CI were not
+ performed. The worktree remains uncommitted.
+
+* Native parser: four independent capability gaps
+
+At parser commit =562dcd428f00b05ca87407fd54b1e04dcf076ae9=, the public native
+=parse-tla-plus-v1= API was probed with this wrapper:
+
+#+begin_example
+---- MODULE Probe ----
+
+====
+#+end_example
+
+| Body | Parse success | First native diagnostic |
+|------+---------------+-------------------------|
+| =Value == TRUE= | true | none; valid artifact and exact byte roundtrip |
+| =Work == INSTANCE WorkLifecycle= | false | =lr-no-action=, state 137, byte 40, =WorkLifecycle= |
+| =Value == Work!Quiescent= | false | =lr-no-action=, state 137, byte 36, =!= |
+| =VARIABLES active,= then newline and = queued= | false | =lr-no-action=, state 205, byte 40, newline |
+| =Value= definition header, then newline and =TRUE= | false | =lr-no-action=, state 209, byte 31, newline |
+
+These are syntax probes, not standalone resolved modules or TLC checks.
+Grammar inspection agrees: only unnamed instance declarations exist, qualified
+operator selection has no production, and newlines terminate declarations.
+This expands the previous first-failure diagnosis; it does not repair the
+parser. No parser-owner source was modified and no downstream fallback added.
+
+Notably the four rejected probes still reported a valid artifact and lossless
+roundtrip. Those properties describe a well-formed diagnostic/lossless token
+artifact; they do NOT mean successful syntax admission. Qualification must
+continue requiring =success AND valid AND exact roundtrip=.
+
+The probe used a bounded =gxi -:max-heap=512M= process with 60-second timeout
+and parent source/dependency paths in =GERBIL_LOADPATH=. An initial attempt
+omitting the parent source path failed at an unbound observability import;
+the corrected path resolved that import without changing dependencies.
+
+* Source cut and next obligations
+
+This continues the uncommitted Aitia source cut at
+=de65fa2033ce62569147ee5411c05c50051206dd=.
+
+| Source | SHA-256 |
+|--------+---------|
+| =proof/tla/EffectAdmission.tla= | =94ab242a5b2610dff134511dd9e4431e9e831d863938bfa30dadea7718a2deb5= |
+| =proof/tla/AssuranceLifecycle.tla= | =8b63b1b334cf31d490630c594aec7de0c30b0a5d4dcc4728d4f9d9c5fbc5bcfc= |
+| =proof/tla/RetirementIngress.tla= | =7227aa8deea008de87e3204c659254d72e2bb153889f5baf2d2993b979067af5= |
+| =proof/tla/RetirementIngress.cfg= | =df8d25edb57d96fc1756167e07b93f95eac717ab6741cbc5a8b0e5149a2c217a= |
+| =proof/tla/RetirementIngressCachedGrant.cfg= | =9c9330666e2bed4cddb2fad63020b9c8432ad573722ca8d18a751e7da253acd0= |
+| =proof/tla/RetirementIngressRejected.cfg= | =7c5725cf1ab1269da358a2366bba7609da0f704787df710dc8cff0a7299d1a51= |
+| =proof/tla/RetirementIngressStarted.cfg= | =b2645bd555d48a306e6a232ce0ecd2a534d3a5ae27b6f8a1d36f15855dcc5b8e= |
+| =proof/tla/RetirementIngressUnfenced.cfg= | =ec3c2181c8714a0408ce4da1430bdb79cc23d30623f6963e99d84673ea5064b0= |
+| =proof/lean/LambdaAitiaProof/Modules/Sdlc/RetirementIngress.lean= | =cc1b7609613461a61ef6825edd39ae055860d99c1691895b437af099c3e27dd0= |
+| =bindings/python/tests/tla_contract.py= | =2dfb943c0dabfe3e97ed4225926f3ce88e60e4f3ce6ddfba5e749ddbd3d1a548= |
+
+1. Repair modular/multiline syntax in the parser owner and require exact
+ source-byte roundtrip. Do not flatten source to disguise missing grammar.
+2. Bind the formal projections to admitted Scheme Contract identity/revision
+ and prove or test the claimed correspondence; hashes alone do not do that.
+3. Challenge atomic fencing/new issuance and repeated/concurrent requests,
+ then state fairness premises for progress. Keep using the existing authority
+ owner rather than adding another authorization implementation.
+4. Keep persistence deferred while these design/verification gates are open.
diff --git a/docs/rfcs/0001-causal-assurance-core.org b/docs/rfcs/0001-causal-assurance-core.org
index 3b49a47..788a0c7 100644
--- a/docs/rfcs/0001-causal-assurance-core.org
+++ b/docs/rfcs/0001-causal-assurance-core.org
@@ -8,23 +8,31 @@
:ID: lambda-aitia-rfc-0001
:STATUS: draft
:DATE: 2026-09-17
+:UPDATED: 2026-09-24
:END:
* Decision summary
-Lambda Aitia owns a domain-neutral, POO-native model of why an engineering
-claim is justified, what defeats or invalidates that justification, which
-verification obligations follow, and which decisions/effects must remain
-blocked. It does not own domain knowledge, a second graph engine, verifier
-implementations, runtime execution or effect authority.
+Lambda Aitia organizes its software-engineering capabilities around the
+complete SDLC, including requirements, design, algorithms and implementation
+strategies, verification/testing, delivery, operation, maintenance and
+retirement. Its POO-native assurance kernel models why an engineering claim is
+justified, what defeats or invalidates that justification, which obligations
+follow, and which decisions/effects must remain blocked. Software semantics
+belong to Aitia's modules; generic graph, temporal/causal and runtime mechanisms
+remain POO Flow-owned, and existing verifiers retain their checking semantics.
The core abstraction is an executable causal assurance case, not a collection
of verification result flags:
-#+begin_example
-claims + assumptions + typed relations + obligations + evidence
-+ invalidation + decisions + effect guards
-#+end_example
+claims, assumptions, typed relations, obligations and evidence, together with
+invalidation, decisions and effect guards.
+
+[[file:0004-sdlc-design-and-causal-trajectories.org][RFC 0004]] refines this architecture with design alternatives, conditional
+guarantees, behavior and design-evolution trajectories, reusable Recipes and
+the deferred MRR direction. Software design is an early leverage point within
+the full lifecycle. The eight-stage assurance/execution flow is a protocol
+used throughout that lifecycle, not a complete SDLC phase list.
* Problem
@@ -51,9 +59,9 @@ through named, checked functions that emit receipts.
| Lambda Aitia | Software/SDLC, ADR and standards modules; claims, assumptions, relation semantics, obligations, evidence admission, invalidation and planning | non-software vertical truth, graph runtime, verifier semantics or effect execution |
| POO Flow | POO composition, functional flow, graph substrate, adapter/runtime handoff | concrete contribution semantics or silent assurance promotion |
| POO Flow Python runtime | generic runtime and lifecycle transport | Aitia bindings, Aitia Scheme semantics or a second SDLC implementation |
-| Aitia Python SDLC Runtime | production SDLC API, Aitia-owned CFFI transport, ABI negotiation and typed receipt projection | reimplementing Scheme decisions or POO Flow's generic Runtime mechanisms |
+| Aitia Python Runtime | production APIs across Aitia's SDLC capabilities, CFFI transport, tool execution integration and typed observations/receipts | reimplementing Scheme decisions or POO Flow's generic Runtime mechanisms |
| Lean | general proof terms and assurance laws | current runtime state or action authority |
-| TLA+/TLC | bounded temporal lifecycle behavior and counterexamples | theorem proving or production execution |
+| Quint/TLC | bounded temporal lifecycle behavior and counterexamples | theorem proving or production execution |
| Cedar | authorization evaluation over admitted input | evidence freshness, proof construction or effect completion |
| Runtime Host | final typed boundary for authorized execution | manufacturing missing evidence or guessing transport/runtime state |
| Application | explicit selection, composition and policy | redefining source-owned semantics |
@@ -74,7 +82,9 @@ explicit descriptor metadata rather than part of those names. The production
Python package validates and materializes lifecycle requests and receipts;
Scheme remains the decision owner. POO Flow's =packages/python-runtime=
remains the separate generic execution/lifecycle mechanism consumed by the
-Aitia SDLC runtime rather than becoming a second SDLC semantic API.
+Aitia Python Runtime rather than becoming a second SDLC semantic API. AnyIO
+provides the runtime-wide concurrency foundation; thread-affine native sessions
+and Host-issued authority must retain their owner at asynchronous boundaries.
* Semantic objects
@@ -106,7 +116,23 @@ commitment state and declared causal parents.
A question that must be discharged for a claim or decision at an exact
snapshot. The obligation states the required evidence kind and capability;
-it does not select or invoke a tool by itself.
+it does not select or invoke a tool by itself. A candidate obligation is
+explicitly bound to the snapshot identity, revision and canonical context
+digest before selection. That context digest excludes only the obligations'
+own binding fields to avoid a self-referential hash; the final snapshot digest
+includes the complete bound obligations. Rebinding an old obligation is not
+replacement-obligation derivation and is rejected.
+The first derivation step emits an inert replacement-requirements receipt
+from an old snapshot, a target snapshot and a change set. It names affected
+old obligations and their structural witnesses, and blocks automatic carryover
+when the source binding, target frontier, claim or policy is not current.
+It does not assign a new obligation identity or claim a replacement exists.
+An explicit second step may bind a caller-authored fresh obligation already
+declared in a target draft with the revised claim and =supports= edge. It
+replays the derivation, rejects non-canonical or unresolved snapshots, and
+returns a final snapshot plus an inert declaration receipt. A changed claim
+blocks automatic carryover but not this explicit declaration; a policy change
+still blocks it. Neither step admits evidence or authorizes an effect.
** Evidence
@@ -147,6 +173,13 @@ that an assurance obligation is discharged, or that an effect is authorized.
=unknown=, and deprecated/superseded records to =stale=. Only a separately
admitted assurance closure and authority receipt can cross the effect boundary.
+Requirements, assumptions, alternatives and conditional guarantees form the
+design context for these choices. A finding can reopen that context before
+implementation changes. Algorithm optimization is evaluated against the chosen
+contract; changing its assumptions or guarantees reopens the design decision.
+The existing design-contract review checks declarations, not arbitrary logical
+implication. RFC 0004 specifies the corresponding qualification work.
+
* Relation planes
** Provenance plane
@@ -157,7 +190,9 @@ itself manufacture a derivation or causal claim.
** Structural dependency plane
-Representative relations: =depends-on=, =implements=, =tests= and =refines=.
+Representative relations: =depends-on=, =composes=, =implements=, =tests= and
+=refines=. =composes= points from a composite claim to a component claim;
+its reverse impact projection uses the shared POO Flow Graph.
This plane supports reverse impact and obligation selection. Its output is
explicitly structural and has no release or runtime authority.
@@ -168,6 +203,13 @@ Representative relations: =precedes=, =causal-parent=, =enables= and
counterfactual. Missing parents and temporal contradictions create an unknown
frontier. Earlier-than never automatically promotes to caused-by.
+POO Flow's Temporal Causality owns generic event graphs, causal cuts, trajectory
+assessment and witness paths. Aitia connects scoped system behavior to the
+evolution of design decisions and their assurance using checked projections.
+Late observations create new assessment cuts; observed failures, model traces
+and counterfactual branches retain their modality and environment. Full Aitia
+trajectory integration is a pending RFC 0004 gate, beyond structural impact.
+
** Assurance plane
Representative relations: =supports=, =discharges=, =defeats= and
@@ -211,15 +253,15 @@ An assurance snapshot binds:
Change processing is pure:
-#+begin_example
-admit change
- -> compute structural witnesses
- -> select affected claims and assumptions
- -> invalidate dependent support
- -> derive obligations
- -> invalidate decisions
- -> emit effect guards
-#+end_example
+#+begin_export mermaid
+flowchart TD
+ C["Change
admitted"] --> W["Structural witnesses
computed"]
+ W --> A["Claims and assumptions
affected set selected"]
+ A --> S["Dependent support
invalidated"]
+ S --> O["New obligations
pending"]
+ S --> D["Dependent decisions
invalidated"]
+ D --> E["Effect guards
blocked"]
+#+end_export
An invalidation receipt reports affected and unresolved identities. Its slots
for temporal assessment, assurance closure, release authorization and runtime
@@ -228,21 +270,101 @@ receipt. Presentation values cannot mutate the admitted snapshot.
* Functional verification planning
-The planner consumes values and returns a value:
-
-#+begin_example
-Plan : Change -> Snapshot -> Policy -> PlanningResult
-#+end_example
+The planner consumes =Change=, =Snapshot= and =Policy= values and returns a
+=PlanningResult= value.
PlanningResult contains ordered obligations, capability requirements,
dependency constraints, rejected alternatives and blockers. Tool invocation
is outside this function. An adapter later consumes one obligation and returns
a typed execution outcome; evidence admission is another separate function.
-
-The first verifier kinds are Lean proof, TLA+/TLC model check, runtime replay,
+The first reported-outcome contract records exact input identity, revision and
+digest bindings and emits an inert structural eligibility receipt. A reported
+success, even when structurally eligible, is not a trusted execution
+attestation. Host-owned adapter identity and effectful admission are required
+before an =AssuranceEvidence= may become authoritative. Its caller-supplied
+=admission-state= is metadata, not proof of that transition. Changing that
+slot after verification would alter the snapshot context and invalidate the
+outcome's original binding. A configured Host therefore issues a separate
+admission object over candidate evidence without editing the snapshot. Its
+private issuance record binds the verifier seal, obligation, evidence, outcome,
+full semantic snapshot, validity interval and source-invalidation epoch.
+Currentness is checked against the Host's configured snapshot source and clock;
+the Host reconstructs the source snapshot to reject stale displayed digests,
+and source changes invalidate earlier seals, including if the old cut reappears.
+This is a Host-local admission candidate, not a decision or effect grant.
+=assurance-host-admitted-support?= consumes that exact current admission with
+one current =supports=/=discharges= assurance relation to its bound obligation.
+It requires the relation to be present in the same Host snapshot and rejects
+copied admissions, speculative modalities, expiration and source invalidation.
+This is one edge witness; it does not close all claim requirements or produce
+an =allow= Decision. Full closure, Cedar policy and Runtime effect gating remain
+separate steps.
+=assurance-host-required-support= now evaluates the claim-declared obligation
+inventory against the Host's current cut. Its typed POO receipt reports one
+blocker per required obligation, rejects missing obligation-to-claim and
+evidence-to-obligation links, and leaves empty/duplicate requirements,
+assumptions, defeaters, unresolved frontiers and challenges visibly blocked.
+Even =complete?= means only that the declared support inventory is witnessed;
+=release-authorized?= remains false. Validity intervals, policy evaluation and
+the actual Decision authority boundary are still separate work.
+The Host may now issue a weak-key-indexed Decision preflight for an exact
+*undecided* Decision node already present in that cut. It checks the
+=depends-on= edge to the supported claim, policy/snapshot/subject bindings and
+current required support; currentness rechecks the Host source epoch and the
+admissions. Copies, revocation, source changes and rollback cannot reuse it.
+This preflight carries =release-authorized? #f= and is not a Cedar result,
+an =allow= Decision or an effect token. The POO Flow Cedar Provider constructs
+inert inputs; the independent Runtime Host still owns policy evaluation and
+effect admission.
+=assurance-host-cedar-request= now rechecks that exact preflight and projects
+its Decision authority, claim subject, policy identity/revision and full
+semantic snapshot digest into POO Flow's POO-native Cedar request family.
+The application supplies the action, intent and POO Flow runtime handoff.
+The resulting request and its context are editable input, not a second Host
+seal; copying or changing them cannot establish Aitia admission. The Runtime
+Host rejects the projected =poo_flow_preflight_only= marker before policy
+evaluation, regardless of its value. This fail-closed check blocks accidental
+promotion of the official preflight path; it does not attest an edited request
+with the marker removed. A future Runtime-owned admission must independently
+bind the source cut at use, evaluate Cedar through both engines and gate the
+effect. The Scheme Host now keeps a weak identity ledger for projected Cedar
+requests. =assurance-host-cedar-request-current?= rechecks the exact issued
+request and the current source cut, rejecting copied or edited presentations,
+revocation, source change and rollback. This is a source-owned predicate, not
+Runtime admission: no Runtime path calls it yet, and its truth cannot authorize
+an effect. The Scheme projection does not evaluate Cedar or gate an effect.
+The POO Flow Cedar capability may require Runtime-owned source admission for
+an action independently of caller context. Aitia's eventual release action
+must use that administrative requirement, and the unmarked request path must
+remain closed until an admitted =allow= Decision and exact current source are
+checked at issue and effect consumption. The present preflight is undecided
+and cannot satisfy that requirement.
+=assurance-host-sealed-support?= is the configured Host seal predicate, not a
+promotion transition.
+The lower-level =assurance-support-sealed-under-adapter?= is conditional on a
+caller-supplied adapter and time; it is not an authority or effect gate.
+It checks structural relationships, the current snapshot, exact
+evidence/outcome bindings and a POO Flow verification seal over the complete
+semantic subject. POO Flow rejects copied or forged adapter presentations as
+well as copied, modified, expired or revoked receipts. This proves issuance by
+the exact adapter instance, not that a caller-supplied adapter was installed
+by a trusted Host. Adapter selection and the evaluation clock must both be
+controlled at the execution boundary; caller-provided values cannot authorize
+an effect. Cedar and Runtime Host remain separate.
+Verifier choice explanation consumes the inert plan and an explicit catalog of
+POO-native candidate declarations. Evidence-kind/capability compatibility and
+declared numeric priority determine a deterministic choice; every rejected
+candidate and unassigned planned obligation remains visible. A chosen
+declaration is not proof that an adapter is installed, executable or trusted.
+
+Verifier integrations include Lean proof, Quint/TLC model check, runtime replay,
unit test, integration test, Cedar differential, static analysis and human
review. This list is extensible through named POO-native helpers, not raw
compute hooks.
+Tool-specific producers qualify against the existing evidence-kind vocabulary;
+the maintained lane does not include TLAPS.
+Reuse the necessary upstream checker and trusted execution boundary before
+adding a new implementation, as specified by RFC 0004.
* Assurance laws
@@ -258,13 +380,24 @@ The architecture requires executable formal statements of at least these laws:
history without an explicit authorized transition.
5. Composition obligation: independently supported A and B do not imply that
compose(A,B) is supported.
+ An explicit =composes= relation identifies the component claims, while a
+ separate obligation must be declared on the composite claim, named in that
+ claim's support requirements, linked by =supports= and bound to the exact
+ snapshot. The composition-requirements receipt only checks this structural
+ declaration; it never admits evidence or grants authority.
6. Unknown blocks closure: an unresolved required frontier cannot grant
authority.
7. Effect separation: authorization neither proves effect start nor effect
completion.
-Lean proves general laws. TLA+/TLC checks lifecycle interleavings such as a
+Lean proves general laws. Quint/TLC checks lifecycle interleavings such as a
source change racing a cached decision or an effect beginning after revocation.
+The bounded lifecycle model additionally separates safety from progress:
+eventual effect start is required only when the source and clock remain stable,
+no revocation intervenes, and the Host weakly fairly schedules every enabled
+stage. Without that scheduling assumption, a stuttering behavior after a grant
+is a valid counterexample. Neither result substitutes for a trusted Host clock,
+adapter identity, or Runtime effect gate.
* Public API direction
@@ -320,7 +453,7 @@ standards compliance, release certification or unrestricted causal inference.
The repository license is closed as =Apache-2.0 AND
LGPL-2.1-or-later=, matching POO Flow. The former open decisions are closed by:
-1. ADR 0004: Aitia owns assurance-specific Lean/TLA+ sources and may consume a
+1. ADR 0004 r2: Aitia owns assurance-specific Lean/Quint sources and may consume a
pinned POO Flow proof package for generic laws.
2. ADR 0007 supersedes ADR 0003: POO Flow is Aitia's public mechanism library;
additions require a stable public owner, not an artificially small
diff --git a/docs/rfcs/0002-snapshot-and-invalidation-kernel.org b/docs/rfcs/0002-snapshot-and-invalidation-kernel.org
index 61a288a..59555d8 100644
--- a/docs/rfcs/0002-snapshot-and-invalidation-kernel.org
+++ b/docs/rfcs/0002-snapshot-and-invalidation-kernel.org
@@ -19,6 +19,10 @@ source, claim, policy and evidence revisions. A change produces an inert
=InvalidationReceipt= through pure traversal of typed relations. The receipt
selects affected current obligations, evidence, decisions and effects; it does
not run a verifier, derive replacement obligations or authorize an effect.
+The snapshot also exposes a non-circular context digest used by Phase 3
+obligation binding. Unlike the final snapshot digest, it omits only each
+obligation's own snapshot-binding slots; it retains all other node, relation,
+revision, policy and frontier content.
* Input contracts
diff --git a/docs/rfcs/0003-composition-value-interface.org b/docs/rfcs/0003-composition-value-interface.org
index 92664db..4c5ab34 100644
--- a/docs/rfcs/0003-composition-value-interface.org
+++ b/docs/rfcs/0003-composition-value-interface.org
@@ -6,215 +6,135 @@
:PROPERTIES:
:ID: lambda-aitia-rfc-0003
-:STATUS: active
+:STATUS: draft
:DATE: 2026-09-18
+:UPDATED: 2026-09-24
:PARENT_RFC: lambda-aitia-rfc-0001
:IMPLEMENTS: lambda-aitia-rfc-0001
:END:
-* Decision summary
+* Decision and present boundary
-Lambda Aitia configuration has Jsonnet-style file-value semantics. A downstream
-=aitia.ss= contains one root =use-composition= expression. Evaluating the file
-returns one closed POO-native Case; the file is not a conventional Gerbil library
-module and the user does not write =import=, =export= or a result binding.
+Lambda Aitia owns Software Engineering Profile values and the maintained
+=aitia= Case. POO Flow owns semantic Module identities, Profile exports,
+=use-module=, =compose profiles=, =user-composition=, Case lowering, admission
+and Session behavior. The ordinary value is POO-native; Aitia does not own a
+parallel selector, catalog, graph or runtime.
-The official root Composition is named =aitia=:
+POO Flow's current public expression surface is:
#+begin_src scheme
-(use-composition aitia)
+(user-composition project
+ (compose profiles
+ (use-module AitiaModule default)))
#+end_src
-The official Composition carries a default Software Engineering Project Profile.
-That Profile composes the Aitia-owned SDLC, NASA NPR 7150.2D knowledge, GitOps,
-ADR/RFC, assurance and formal-method obligations, including TLA+ model checking.
-These Modules and Profiles are implicit in the ordinary user surface. Selecting
-=aitia= does not require the downstream project to restate or import them.
+The maintained Aitia owner provides =AitiaModule= and its already composed
+=aitia= value in =user-interface/compositions/aitia.ss=. The default Project
+declares GitOps, SDLC, NASA, ADR/RFC, assurance and formal-method selections.
+Declaration alone neither executes a verifier nor establishes compliance or
+release authority.
-* User experience
+The former =(use-composition aitia)=, implicit catalog and single-expression
+file loader were removed from POO Flow's public surface by its native Profile
+algebra migration. Their earlier tests no longer qualify the current API.
+The no-import, one-expression downstream convenience requested for ordinary
+users remains open. Aitia will not recreate the removed loader locally or
+present an untested downstream snippet as supported.
-The common path exposes Project facts and named Composition slots rather than the
-POO meta-object protocol:
+* Namespace and edition
-#+begin_src scheme
-(use-composition aitia
- (project
- repository: "tao3k/example"
- languages: '(rust python))
- (delivery
- provider: github))
-#+end_src
-
-The clauses are hygienic syntax projected onto ordinary POO-native objects and
-slot operations. They are not an alist, JSON or directory-shaped runtime DSL.
-The resulting value remains a normal POO Flow Scenario Case.
+The canonical NASA standard-family identity is =nasa/sdlc/npr-7150.2=:
+organization =nasa=, domain Module =sdlc=, Profile =npr-7150.2=. The selected
+standard object explicitly carries =edition: D=, effective date and source
+URLs. Its versioned Source Lock uses exact edition, source digests and byte
+counts. A future edition changes the bound source and assessment evidence,
+not the public family namespace or Scheme/Python import path.
-The default UI does not require knowledge of prototypes, C3 linearization,
-=.mix=, =super=, ExecutionPlan or Session. A user who needs a non-default model
-may explicitly extend the default Project Profile with the reviewed POO-native
-override surface:
+The maintained GitOps Scenario selects this typed Module Profile with:
#+begin_src scheme
-(use-composition aitia
- (.def (project @ default-software-engineering-project)
- (reviewers =>.+ '(security release))))
+(use-module NasaSdlcModule npr-7150.2)
#+end_src
-This is progressive disclosure: POO remains the implementation and extension
-model, but it is not prerequisite knowledge for selecting the maintained
-Composition and supplying ordinary Project facts.
-
-* File-value contract
-
-An =aitia.ss= input MUST contain exactly one root value expression. The POO
-Flow Composition value loader expands that expression in a bounded prelude
-which supplies only the public Composition syntax, an explicitly provided
-official Composition catalog and the reviewed POO override operators. Aitia
-contributes the =aitia= catalog entry; it does not own or wrap the loader.
-Loading MUST NOT depend on a user-written import or export and MUST NOT discover
-semantics by recursively importing a directory.
-
-The POO Flow loader MUST:
-
-1. read and expand the single root expression with source locations preserved;
-2. resolve its Composition identity from the Composition catalog;
-3. apply ordinary slot clauses and then explicit advanced POO overrides;
-4. recompute derived slots and reject unresolved conflicts;
-5. validate the closed object graph; and
-6. return exactly one Case without starting a Runtime Session or executing an
- effect.
-
-=aitia doctor= consumes the same value path. It may present effective values,
-linearization and per-slot provenance, but it does not create a second
-configuration representation.
-
-* Composition and Module namespaces
-
-Composition identities and Module identities are distinct typed namespaces.
-The public official Composition identity =aitia= is reserved to the Lambda Aitia
-project. A user-authored Composition MUST declare a user-owned namespace and
-MUST NOT:
-
-- use =aitia= as its namespace;
-- equal any registered Module identity;
-- shadow an official Composition identity; or
-- rely on import order to select between a Composition and a Module.
-
-The expander rejects these conflicts before Case construction. Catalog lookup
-never falls back from the Composition namespace to the Module namespace.
-
-The exact textual spelling of user-owned qualified identities is owned by the
-implementation slice, but the semantic identity MUST contain a namespace and a
-local name. An unqualified identity is permitted only for an official reserved
-Composition such as =aitia=.
-
-* Official =aitia= Composition
-
-The official Composition is a maintained object graph, not an empty template.
-Its default Project Profile owns at least these slots:
-
-| Slot | Default owner | Meaning |
-|------+---------------+---------|
-| =project= | Lambda Aitia | Software Engineering Project facts and lifecycle |
-| =sdlc= | Lambda Aitia | SDLC structure and review obligations |
-| =standards= | Lambda Aitia | NASA NPR 7150.2D applicability knowledge |
-| =delivery= | Lambda Aitia | GitOps development, staging and production flow |
-| =decisions= | Lambda Aitia | ADR and RFC relationships |
-| =assurance= | Lambda Aitia | Claims, obligations, evidence and invalidation |
-| =formal-methods= | Lambda Aitia | TLA+ and Lean obligation projections |
-| =graph-policy= | POO Flow | Execution, impact and temporal graph projection |
-| =runtime-policy= | POO Flow | Case admission and fresh Session construction |
-
-The default does not claim universal NASA compliance, successful model checking,
-proof completion or release authority. It installs the corresponding knowledge,
-obligations and gates. Receipts establish what was actually evaluated.
-
-* Ownership boundaries
-
-Lambda Aitia owns the Software Engineering domain model, the official =aitia=
-Composition catalog entry, its default Project Profile and its qualification
-Scenarios.
-
-POO Flow owns =use-composition=, the single-expression value loader, typed
-Composition identities and catalog resolution, generic POO composition
-mechanisms, C3 behavior, Graph and ExecutionPlan projection, impact and temporal
-algorithms, Case admission, Session state and runtime receipts. Aitia MUST
-reuse those public mechanisms and MUST NOT implement a second selector, loader,
-graph, planner or runtime engine.
-
-The Python project under =bindings/python= is a production SDLC Runtime consumer.
-It consumes the closed Case projections and C ABI receipts; it does not parse a
-parallel Python configuration model or redefine Composition semantics.
-
-* Scenario boundary
-
-=user-interface/scenarios/= contains Aitia-owned reference, qualification and
-regression assets. Each Scenario invokes the same public Composition value path
-as downstream users and adds fixed inputs plus expected projections and receipts.
-It is not a directory contract copied into downstream projects.
-
-Downstream projects normally contain only =aitia.ss=. Advanced users may split
-ordinary Scheme implementation files for their own maintenance, but the root
-configuration still evaluates to one value and directory shape does not acquire
-configuration semantics.
-
-* Slot resolution
-
-C3 determines deterministic prototype precedence. Each public slot also owns a
-declared resolution policy; linearization alone does not imply merge behavior.
-The initial policies are:
-
-- scalar facts replace the inherited value;
-- ordered collections append with identity-based de-duplication;
-- keyed objects merge only through their owning POO protocol;
-- assurance obligations are monotone unless an explicit authority-governed
- waiver object is admitted;
-- derived slots are recomputed after overrides and are not directly writable;
-- incompatible Profile selections fail closed.
-
-The resolved Case retains origin metadata sufficient for =aitia doctor= to show
-which official Profile or downstream override contributed an effective slot.
-
-* Implementation sequence
-
-1. POO Flow supplies a source-located single-expression value loader and changes
- the downstream =use-composition= surface from declaration/export semantics to
- expression semantics. Existing internal Case builders remain the lowering
- target.
-2. Lambda Aitia publishes the official =aitia= Composition and default Software
- Engineering Project Profile with the implicit Module closure described above.
-3. Aitia qualification Scenarios exercise zero-override, ordinary-slot and
- advanced-POO paths through the same loader.
-4. =aitia doctor= projects value, provenance, obligations and Plan summaries from
- the closed Case.
-5. Python Runtime and C ABI consume the same canonical Case projections without
- introducing another configuration source.
-
-There is no compatibility layer preserving directory discovery or explicit
-downstream export syntax. Migration is direct because the former surface had
-not established the desired downstream contract.
+Its check name is a workflow gate keyed to the standard family. A green
+check with only that name now fails closed: the check must also match the
+selected Standard object's edition and Source Lock digest. These check
+fields are still caller-supplied workflow data, not an Assurance seal or
+proof of actual verifier inputs. Accountable Host admission and source-byte
+binding remain separate acceptance gates.
+
+When an application configures an Assurance Host source reader, issuance and
+currentness recheck all declared verifier inputs against current source bytes.
+The configured verification operation receives Host-materialized POO input
+values as a fourth argument, including a copy of each validated payload; the
+Host rechecks identities, revisions, digests and payloads after the operation.
+Without a reader, the operation keeps its three-argument contract.
+Observed byte drift invalidates prior admissions, so returning to the old bytes
+requires fresh verification. This strengthens the Host boundary but does not
+authenticate the source repository or prove what a verifier process actually
+consumed; those remain application Host responsibilities.
+
+* Composition semantics
+
+The default path composes maintained POO Profile values; advanced users may
+extend those values with ordinary POO slot operations before composition.
+Profile selection preserves Module definition, instance, Profile identity,
+revision and generation in POO Flow selection proofs. The resulting Case owns
+one execution-plan projection and fresh Sessions. Importing or composing
+values has no Runtime effect.
+
+The official =aitia= Case is a maintained object graph, not an empty template.
+Its Project Profile declares Software Engineering responsibilities and
+selection of GitOps, SDLC, NASA, ADR/RFC, assurance and formal-method
+obligations. The Project declaration does not yet prove that all downstream
+slots, verifier bindings and no-import UX are complete.
+
+[[file:0004-sdlc-design-and-causal-trajectories.org][RFC 0004]] places these selections inside the complete SDLC. Design alternatives,
+implementation strategies, testing, operation and revision use the same
+maintained composition boundary. A Recipe composes existing POO declarations
+for applicability, source-bound assets, operations and required checks. An
+advanced replacement must preserve those checks or open explicit obligations;
+changing a Profile cannot silently weaken a requirement or reuse stale support.
+Recipe qualification and behavior/design trajectory projection remain pending.
+
+* Ownership and trust
+
+| Owner | Responsibility |
+|-------+----------------|
+| Aitia | Software Engineering/NASA semantics, maintained Profiles and Case, qualification scenarios |
+| POO Flow | Module/Profile algebra, composition, Graph, Case admission, Session and Runtime handoff |
+| Application Host | source reads, verifier execution, authority and effects |
+
+The Python project under =bindings/python= consumes Aitia's native C ABI; it
+does not parse a second configuration model or recreate SDLC semantics.
+Versioned NASA source identities are provenance facts, not namespace segments
+that ordinary users must type.
* Acceptance gates
-- [ ] An =aitia.ss= containing only =(use-composition aitia)= evaluates to one
- admitted Case without explicit import, export or binding forms.
-- [ ] The zero-override Case includes GitOps, NASA SDLC, ADR/RFC, assurance and
- formal-method obligation slots through the default Project Profile.
-- [ ] An ordinary Project clause lowers to POO-native slot operations.
-- [ ] An advanced POO override extends the same default Project Profile.
-- [ ] Composition/Module namespace collision and use of the reserved =aitia=
- namespace by a user Composition fail at expansion.
-- [ ] Loading and =doctor= perform no Runtime effect.
-- [ ] Reference, qualification and regression Scenarios use the public value
- path rather than private constructors.
-- [ ] Direct compilation, atomic behavior tests, a real Aitia Scenario and the
- existing composition macro scale gate pass with exact receipts.
-
-* Relationship to other decisions
-
-- RFC 0001 defines the causal assurance vocabulary carried by the default
- Composition.
-- RFC 0002 defines the immutable AssuranceSnapshot and invalidation boundary.
-- ADR 0007 keeps POO Flow as the public mechanism library consumed here.
-- ADR 0006 keeps Python as the production SDLC Runtime rather than a second
- configuration authority.
+- [X] Official Aitia and NASA identities lower through POO Flow's public
+ =user-composition=, =use-module= and Profile algebra.
+- [X] The maintained Aitia Case and GitOps/NASA Scenario construct without
+ Runtime execution and have focused tests.
+- [ ] A zero-override downstream file can select =aitia= without imports or
+ exposing POO internals, through a POO Flow-owned public mechanism.
+- [ ] An advanced override extends the same maintained Project Profile and
+ preserves selection/provenance receipts.
+- [X] The pure GitOps decision rejects a same-name green check lacking the
+ selected edition or Source Lock digest, including a check for another edition.
+- [ ] A Host-issued NASA assessment binds actual verifier inputs, source bytes,
+ exact edition and Source Lock digest before it can count as Assurance support.
+- [ ] Case, C ABI and Python project consume one canonical composition cut in
+ a clean checkout across both repositories.
+- [ ] Source-located diagnostics, doctor presentation and scale receipts pass
+ for the current POO-native expression path.
+
+* Related decisions
+
+- RFC 0001 defines the Assurance vocabulary carried by the Project Profile.
+- RFC 0002 defines canonical snapshots and invalidation.
+- RFC 0004 defines lifecycle design, trajectory scope and reusable Recipes.
+- ADR 0007 keeps POO Flow as the mechanism library.
+- ADR 0006 keeps Python as a production Runtime consumer, not a second
+ semantic owner.
diff --git a/docs/rfcs/0004-sdlc-design-and-causal-trajectories.org b/docs/rfcs/0004-sdlc-design-and-causal-trajectories.org
new file mode 100644
index 0000000..fc3516c
--- /dev/null
+++ b/docs/rfcs/0004-sdlc-design-and-causal-trajectories.org
@@ -0,0 +1,612 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: RFC 0004 — SDLC Design, Causal Assurance and Trajectories
+#+startup: overview
+
+:PROPERTIES:
+:ID: lambda-aitia-rfc-0004
+:STATUS: draft
+:DATE: 2026-09-24
+:UPDATED: 2026-09-24
+:PARENT_RFC: lambda-aitia-rfc-0001
+:IMPLEMENTS: lambda-aitia-rfc-0001
+:END:
+
+* Decision summary and scope
+
+The complete Software Development Life Cycle (SDLC) is Aitia's organizing
+framework. Requirements, engineering design, algorithm and implementation
+strategies, verification and testing, delivery, operation, maintenance and
+retirement all participate in that lifecycle. Causal Assurance maintains the
+justification of engineering conclusions as these activities change the system.
+
+Software design receives early engineering investment because cheaper code
+generation increases the value of deciding what to build, under which
+assumptions, with which responsibilities and guarantees. This is a product
+direction to evaluate through qualification cases, not a claim that code cost
+or design quality has already been measured.
+
+The eight-step =change -> invalidate -> plan -> verify -> admit -> decide ->
+authorize -> effect= flow is the existing assurance/execution protocol. It can
+be used at multiple lifecycle points; it is not an enumeration of the entire
+SDLC. The overall lifecycle contains feedback, concurrent activities and
+repeated revisions. A bounded execution plan may be a DAG without turning the
+whole lifecycle into one fixed pipeline.
+
+This RFC records the agreed direction and pending executable acceptance work.
+Its draft status does not promote the capabilities below to implemented APIs.
+
+[[file:0005-org-native-engineering-workspace.org][RFC 0005]] specifies the native Org engineering work surface and source-bound
+Agent review, with generic Org integration owned by POO Flow. Its initial
+authoring checks are distinct from the pending connected execution loop.
+
+The lifecycle workflow below names engineering states, not implementation
+milestones. Transitions require attributable revisions and receipts; an arrow
+does not itself grant assurance or authority. Feedback can reopen earlier
+activities, and independent obligations may proceed concurrently.
+
+#+begin_export mermaid
+stateDiagram-v2
+ state "Requirements: scoped" as Requirements
+ state "Design: alternatives proposed" as Design
+ state "ADR: option selected" as Selected
+ state "Implementation: candidate" as Implementation
+ state "Verification: obligations pending" as Verification
+ state "Assurance: supported at a bound cut" as Supported
+ state "Delivery: effect authorized" as Authorized
+ state "Operation: behavior observed" as Operation
+ state "Maintenance: reassessment pending" as Reassessment
+ state "Retirement: obligations pending" as Retirement
+ state "Retired: closure recorded" as Retired
+ [*] --> Requirements
+ Requirements --> Design: scope and constraints recorded
+ Design --> Selected: alternatives reviewed
+ Selected --> Implementation: strategy chosen
+ Implementation --> Verification: candidate and checks bound
+ Verification --> Supported: scoped evidence admitted
+ Verification --> Design: finding challenges design
+ Supported --> Authorized: current decision and effect gate
+ Authorized --> Operation: execution receipt and observations
+ Authorized --> Reassessment: revoked, expired or premise changed
+ Supported --> Reassessment: premise changes or late evidence
+ Operation --> Reassessment
+ Reassessment --> Requirements: requirement revision
+ Reassessment --> Design: decision revision
+ Reassessment --> Verification: renewed obligations
+ Operation --> Retirement
+ Retirement --> Retired: residual obligations discharged
+#+end_export
+
+These states summarize activity, not a single global status enum. Multiple
+claims may have different support states at the same cut. Pending or stale
+support blocks the affected effect; an accepted ADR alone is not authorization.
+Operational findings or changed context trigger reassessment; an accountable
+retirement decision opens retirement obligations before closure is recorded.
+
+* Formal challenge gates before persistence integration
+
+The immediate design-validation priority is to challenge lifecycle assumptions
+using reviewed upstream failure reports, not to add storage. Keep reported
+behavior, our proposed transfer and locally executed evidence distinct in the
+[[file:../plans/engineering-case-source-inventory.org][source inventory]]. An Issue being closed does not qualify our design.
+
+Quint is organized into reusable work and grant predicates with independently
+runnable finite models and a separate ingress composition lane. Each safety boundary
+needs positive exploration, a reachable-state control and a deliberately
+weakened guard that produces the expected counterexample. Do not duplicate a
+shared action in every scenario, discard environment interference to obtain a
+green atomic test, or infer composite correctness from leaf checks alone.
+
+The [[file:../../proof/quint/README.org][maintained Quint slice]] separates cancellation/work observations from retirement
+review. It requires final-cut quiescence and known inventory: a cancellation
+request, an earlier accepted review, or missing history is insufficient.
+Lean proves safety preservation for an abstract retirement transition relation;
+it does not yet prove Scheme or Quint refinement. The
+[[file:../receipts/2026-09-24-retirement-formal-review.org][historical TLA+ receipt]] records the earlier checks and parser boundary.
+
+The historical [[file:../receipts/2026-09-24-formal-source-qualification.org][source qualification]] checked TLA+ syntax/byte-roundtrip. The
+maintained Quint CLI checks Quint syntax and binds both current source views
+to one POO design declaration, revision and
+source cut using existing SDLC/Assurance links. A model, proof or premise
+change invalidates old bindings. This is source correspondence, not a proof of
+semantic refinement or Host evidence admission. Independent replay and
+semantic correspondence remain required for closure.
+The isolated review terminal state assumes no later
+ingress; the [[file:../receipts/2026-09-24-retirement-ingress-review.org][bounded ingress composition]] challenges this by permitting requests
+after retirement. It requires closing grant issuance and advancing the epoch
+before finalization, and rechecking the cached grant at effect start. The
+effect-admission predicate is extracted from the existing assurance model.
+This does not establish a deployed Cedar/Runtime binding, new grant issuance,
+distributed atomicity, repeated retries or eventual progress. Authorization,
+ingress exclusion and fairness still need their actual owner correspondences
+before any Runtime or eventual-retirement guarantee. Persistence remains
+deferred; passing these abstract checks does not authorize its implementation.
+
+** Correspondence gate across design, formal views and execution
+
+A source digest proves which bytes were checked, not that the checked formula
+means the declared guarantee. Each claimed correspondence therefore needs a
+reviewable interpretation, an owner and an adversarial check:
+
+| Boundary | Required correspondence | Falsifying control |
+|----------+-------------------------+--------------------|
+| POO design contract → Quint | Map each relevant assumption and guarantee to named state predicates/actions, including the environment and modeled client bound | weaken a fence, inventory or ingress premise and require the expected reachable counterexample |
+| POO design contract → Lean | State theorem premises and conclusion against the same declaration; audit imported axioms and excluded behaviors | remove a needed premise and demonstrate that the claim is no longer established |
+| Quint/Lean → Scheme and Runtime | Identify the concrete state/effect transitions represented by the abstract ones and document omitted transitions | replay a concrete behavior outside the mapping and keep conformance unknown or violated |
+| Tool result → Assurance Host | Bind actual tool/source/configuration identities and the exact proposition assessed | change an input or tool identity and reject reuse of the earlier admission |
+
+The current source-group links qualify identity and invalidation only. A
+matching name, digest, passing model configuration or axiom-free theorem does
+not fill an interpretation row. A counterexample in the abstract model is a
+model finding until its concrete correspondence is checked; an observed
+Runtime trace is not retroactively turned into a model execution. If a
+concrete transition has no modeled counterpart, record that coverage gap and
+reopen the design or model rather than narrowing the tested environment to
+make the check pass. No implementation-conformance or effect-authority flag
+may become true from these source mappings alone.
+
+For the maintained =job-executor/retirement= declaration, the first
+correspondence review has these concrete boundaries:
+
+| Declaration | Current formal representation | Remaining gap |
+|-------------+-------------------------------+---------------|
+| =atomic-epoch-fence= | Quint =CloseIngress= closes issuance and advances =epoch= in one action; Lean =Step.close= changes both fields | No concrete Host/Cedar/Runtime atomic transition is bound |
+| =known-inventory= and quiescence | Quint =Retire= requires =known= and =quiescent(work)=; Lean =Retirement.Step.finalize= requires =inventory=, =idle= and =drained= | Neither formal view proves the inventory is complete in production |
+| =no-new-grant= | The Quint composition has one fixed cached grant and no grant-issuance action; Lean has no issuance transition | This is an excluded environment behavior, not a proved revocation protocol |
+| =bounded-client= | Quint =pending= and =attempted= model one request/attempt; Lean's =requestOrReject= stutters | Multiple clients, responses and retry progress are outside the Lean theorem |
+| =retirement-safety/g1= | Quint checks =NoOutstandingWork= and =NoPostRetirementStart=; Lean proves =Retirement.Safe= and =RetirementIngress.Safe= for its own =Step= relation | These are two abstract checks, not a proof that either is a faithful refinement of the Scheme contract or deployed effects |
+
+The current source binding tracks changes to these files, but none of the
+remaining gaps can be closed by adding another digest link. In particular,
+the absent grant-issuance transition must be modeled and challenged before
+claiming that real new grants are excluded after retirement.
+
+* Design and implementation within the lifecycle
+
+| Activity | Engineering question | Attributable output |
+|----------+----------------------+---------------------|
+| Requirements and context | What outcome is needed, for whom and under what constraints? | revisioned requirements, scope and acceptance conditions |
+| Software and engineering design | Who owns state, responsibility, interfaces, failure handling and effects? | alternatives, assumptions, guarantees and RFC/ADR decisions |
+| Algorithms and implementation strategies | How can the chosen guarantees be realized within cost and resource constraints? | strategy choice, implementation mapping and measurements |
+| Verification and testing | Which precise propositions have supporting or contradicting evidence? | scoped proofs, model checks, tests, replay and review |
+| Delivery and operation | Which action is currently permitted, and what actually happened? | authority, execution receipts and observed trajectories |
+| Maintenance and retirement | Which assumptions changed, and what must be revised, migrated or stopped? | invalidation, new obligations and accountable successor decisions |
+
+Activities share one lifecycle subject and revision history. An algorithm
+change that alters consistency or failure guarantees is also a design
+decision. A performance optimization within the same contract still needs
+conformance and performance evidence. Estimated cost, human judgment and
+measured behavior retain distinct evidence kinds and scopes.
+
+Design work relates existing POO values through:
+
+#+begin_export mermaid
+flowchart TD
+ R["Requirements and constraints
scoped"] --> A["Assumptions and alternatives
proposed"]
+ A --> D["Responsibilities, state, interfaces and effects
selected in RFC / ADR"]
+ D --> G["Conditional guarantees
declared, not yet supported"]
+ G --> I["Implementation strategy
candidate"]
+ G --> O["Verification obligations
open"]
+ I --> E["Tool results and observations
candidate evidence"]
+ O --> E
+ E --> C{"Scoped admission and assessment"}
+ C -->|support established| S["Guarantee supported
at this revision and cut"]
+ C -->|failed or unresolved| F["Finding / unknown frontier
requires review"]
+ F --> A
+ S -->|premise changes| X["Prior support stale
reassessment required"]
+ X --> O
+#+end_export
+
+A guarantee states its subject, predicate and scope under named assumptions.
+An assumption states a premise, its owner and the conditions requiring review.
+An ADR records considered options and their consequences; acceptance records
+the design choice, while current assurance requires separately admitted
+evidence. Relaxing a requirement is an explicit requirement revision and
+review, not a repair technique that silently turns a failed check green.
+
+The existing =SdlcDesignContract= supplies responsibilities, owned state,
+allowed effects, assumptions, guarantees, variations and a decision identity.
+Its replacement review admits exact declaration comparisons only. Logical
+implication between different statements, model/code correspondence and
+cross-component guarantees require additional obligations with attributable
+checks. Neither name equality nor two successful component checks establishes
+the safety of their composition.
+
+* System behavior and design evolution trajectories
+
+POO Flow owns Temporal Causality, event graphs, causal cuts, trajectory
+contracts, witness paths and traversal. Aitia supplies software event meaning
+and the links from behavior to requirements, assumptions, design decisions and
+assurance. Python transports observations and invokes tools through the owned
+runtime boundary.
+
+Two related trajectories are required:
+
+- system behavior: requests, state transitions, retries, effects, failures and
+ recovery in an explicitly identified environment;
+- design and assurance evolution: hypotheses, RFC/ADR choices, checks,
+ findings, invalidation, revision and renewed acceptance.
+
+Evidence and checked projections connect these trajectories. A runtime event
+can challenge an assumption; a reviewed explanation can motivate an ADR
+revision. Structural impact, temporal precedence and graph reachability do not
+by themselves establish a causal explanation. Agent-proposed explanations
+remain hypotheses until their supporting checks are admitted.
+
+#+begin_export mermaid
+flowchart TD
+ subgraph Behavior["System behavior trajectory"]
+ O["Execution events
observed in a bound environment"] --> R["Replay / checks
scoped results"]
+ end
+ subgraph Design["Design and assurance trajectory"]
+ G["Guarantee
declared"] --> F["Finding
open"]
+ F --> H["Causal explanation
hypothesized"]
+ H --> C["Explanation checks
pending"]
+ C --> A["ADR revision
proposed for review"]
+ A --> N["Revalidation obligations
pending after selection"]
+ end
+ R -->|challenges within scope| F
+ R -->|evidence for assessment| C
+ N -->|requires a new bound run| O
+#+end_export
+
+The cross-trajectory links are evidence and obligation relations, not an
+automatic causal inference from event order. A proposed ADR retains its
+assumptions even when the explanation remains unresolved.
+
+| Trajectory source | Scope that must remain visible |
+|-------------------+--------------------------------|
+| Production or experimental execution | observed events in the named system or harness, with source and environment bindings |
+| TLC/model-check counterexample | behavior permitted by the exact model and checked configuration; implementation correspondence is a separate obligation |
+| Proposed intervention or alternative design | counterfactual branch under explicit assumptions and a bounded horizon |
+| RFC/ADR and assurance evolution | recorded choices and changes in justification, without rewriting earlier facts or granting runtime authority |
+
+An observed failure remains observed in its original environment. The current
+POO Flow trajectory contract reserves =error-event-paths= for uncommitted
+counterfactuals; actual bug replays must not be relabeled to fit that slot.
+Use the existing observed event graph, and place any missing generic trajectory
+mechanism in its POO Flow owner before exposing an Aitia projection.
+
+An assessment binds the event graph, immutable causal cut, explicit time
+observation, source/design/policy revisions and horizon. Occurrence order,
+ingress order and revision order must not be conflated. A late observation
+creates a new assessment cut and may invalidate current support while leaving
+the earlier cut intact. Missing causal parents, incompatible clock domains
+and incomplete coverage retain an unknown frontier. Cross-clock mapping or
+additional time axes require a qualified POO Flow contract; this RFC does not
+claim that every such mapping already exists.
+
+* Lifecycle Impact of introduction, change and retirement
+
+Design direction agreed on 2026-09-24; implementation qualification remains
+pending. Impact is a lifecycle view of a change, not only a list of files to
+rebuild. It asks which historical interpretations and obligations, current
+conditions, and future options are changed by introducing, modifying or
+retiring a capability. POO Flow retains generic Impact and Temporal Causality
+ownership; Aitia supplies Software/SDLC relations and domain obligations.
+
+| Perspective | Introduce or modify a capability | Retire a capability |
+|-------------+---------------------------------+---------------------|
+| Past assets and commitments | check old data against new assumptions; reassess the applicability of earlier evidence | preserve interpretation/replay of old results and outstanding commitments |
+| Current code and operation | inspect consumers, interfaces, deployment conditions, authority and resource interactions | identify remaining consumers, running instances, in-flight work and configuration references |
+| Future work and obligations | open verification, migration, monitoring and maintenance obligations | migrate consumers, prevent new use where required, drain/cancel queued work and verify residual duties |
+
+Impact on the past never changes an observed historical fact. Distinguish
+evidence that was valid under its original conditions but is no longer
+applicable from evidence whose original justification is now challenged.
+The latter opens correction and retrospective review, not silent deletion of
+the original receipt. Record when a condition applied separately from when
+it was learned or corrected.
+
+** Inputs, meaning and receipt boundary
+
+Assessments bind the proposed change and before/after source/design snapshots,
+affected capability identities, deployment scope, selected typed relations,
+explicit causal cut/time observation, horizon and inventory completeness.
+An unexecuted change remains proposed or counterfactual; it is not an observed
+production event merely because its impact has been computed.
+
+Relevant relations include implementation dependencies, data/protocol usage,
+assumption/guarantee support, evidence applicability and accountable ownership.
+Every relation needs a source and declared meaning. Org ancestry, organization
+hierarchy, code references and causal relations are not interchangeable.
+An unchanged function signature does not imply an unchanged guarantee: a
+retention, permission or consistency change can alter assurance without an
+API change.
+
+For each affected subject, present its identity, witness path, applicable
+versions/environment/time bounds, evidence modality, unresolved conditions,
+recommended obligation and responsible role. This is an analysis/proposal,
+not execution authority. Existing Cedar/Runtime admission remains necessary
+before a proposed migration, cancellation or other external effect.
+
+The existing POO Flow classifier partitions events by logical position.
+That alone does not establish current exposure: an old task, authorization or
+stored object can still be active now. Current applicability needs an explicit
+state or validity-interval projection, including its source and uncertainty.
+Any missing generic mechanism belongs in POO Flow, not a parallel Aitia engine.
+
+Dynamic callers, unregistered deployments, external consumers and missing
+history remain an unknown frontier. No discovered dependency is not evidence
+of no dependency. Completeness is relative to the declared inventory and scope.
+
+** Retirement preserves history and discharges remaining obligations
+
+Deletion is not the inverse of introduction. Analyze the old snapshot as well
+as the candidate one: deleting an object and its edges before analysis can
+hide precisely the consumers that need migration. Preserve the capability's
+historical identity and provenance after removal from the active surface;
+record retirement and any successor explicitly. A semantically different
+replacement does not inherit identity merely by keeping the same display name.
+
+The maintained Composition selects applicable retirement stages. The following
+workflow is a qualification target, not an implemented global state machine:
+
+#+begin_export mermaid
+flowchart TD
+ P["Retirement
proposed"] --> I["Impact
scoped; unknowns retained"]
+ I --> R["Migration and residual duties
owner review pending"]
+ R --> A["Selected actions
authorization pending"]
+ A --> E["Migration / drain / cancellation
execution receipts required"]
+ E --> V["Residual obligations
verification pending"]
+ V -->|unresolved| R
+ V -->|closure conditions satisfied| C["Retired
historical identity retained"]
+#+end_export
+
+An accepted retirement proposal neither cancels queued work nor rewrites
+historical evidence. Required operations use the existing effect boundary;
+irreversible effects may require explicit compensation or unresolved duties
+rather than a claim that rollback erased them.
+
+** Next qualification: retire one job-executor capability
+
+Pair the existing repair case with a bounded retirement experiment using the
+same job-executor subject. Select one concrete capability and bind its consumers
+before implementing the experiment; this RFC does not order a production
+removal. Include an old persisted job/result, a current consumer or in-flight
+job, and queued future work. Preserve one unrelated consumer as a negative
+control and one deliberately unknown external-consumer boundary.
+
+The experiment must show pre/post snapshot witnesses, explicit handling of
+still-active old work, proposed migration/drain obligations and actual receipts
+where execution is claimed. Replaying an old result must retain its original
+version/scope; a replacement cannot silently acquire the old evidence.
+
+The [[file:../receipts/2026-09-24-retirement-impact.org][first bounded analysis receipt]] exercises the automatic-retry fixture through
+POO Flow's existing public mechanisms. It uses synthetic facts and does not
+close the persisted-state, obligation derivation or execution requirements above.
+
+* Historical engineering cases as design inputs
+
+Issues, design discussions, incident reports and engineering articles can
+contribute before implementation begins. Preserve the reasoning that connects
+context, requirements, assumptions, alternatives and consequences, rather than
+only a summary or a recommended pattern. Include successful designs, rejected
+proposals and withdrawn fixes as well as failures.
+
+| Use | Contribution | Admission boundary |
+|-----+--------------+--------------------|
+| Inspiration | proposes a question, assumption, alternative or failure scenario | similarity does not establish applicability or a local defect |
+| Judgment | supports an accountable choice under explicit conditions and tradeoffs | record matching conditions, differences, counterevidence, owner and review triggers |
+| Verification asset | supplies a model, checker, harness or regression | qualify the exact asset and proposition before admitting support for the current subject |
+
+These are uses, not automatic stages of increasing trust. A useful article
+need not contain a replay program. A human cost or maintainability judgment
+need not become a mechanical theorem, but must retain attribution, scope and
+reconsideration conditions. Neither judgment nor author reputation grants an
+effect capability.
+
+The reusable case records context/version/workload, intended guarantee,
+explicit and inferred assumptions, reported events, proposed explanations,
+considered alternatives, consequences and transfer conditions. Every extracted
+claim points to its source passage or comment and available revision. Keep
+original URL, author, publication/update and retrieval times; record digest and
+reuse permissions when retaining source bytes or executable assets. A mutable
+URL is not a pinned artifact. This phase references sources and records our
+analysis; it does not vendor articles or assume all public material is licensed
+for code/data redistribution.
+
+Before applying a case, compare state/effect ownership, failure model,
+identity, timing/retention, workload and recovery requirements against the
+current design. Record the correspondence as a reviewable proposal with
+supporting and contradicting observations. Search similarity, citation count,
+Issue closure and Agent confidence cannot discharge that applicability review.
+Multiple articles repeating the same incident are not independent evidence.
+
+#+begin_export mermaid
+flowchart TD
+ S["External material
reported and attributed"] --> Q["Design question / alternative
proposed"]
+ Q --> M["Applicability mapping
pending review"]
+ M --> J["Design judgment
scoped, with explicit unknowns"]
+ J --> D["RFC / ADR choice
proposed for accountable review"]
+ D --> O["Verification obligations
open"]
+ S --> A["Reusable assets
unqualified"]
+ A --> V["Actual checks
candidate results"]
+ O --> V
+ V --> E["Evidence admission
required for supported claims"]
+ S --> N["Correction / new report
applicability reassessment pending"]
+ N --> M
+#+end_export
+
+An external report is evidence of what its author reports, not a local observed
+execution. Separate the system-event timeline from the history of what became
+known: publication, correction, retrieval and design review. A later explanation
+creates a new assessment and can reopen a decision; it does not rewrite the
+earlier knowledge cut. Unknown timestamps remain unknown. Do not give a whole
+case one =verified= flag: a reported failure, hypothesized cause, tested repair
+and unreviewed transfer can coexist.
+
+Use existing Artifact, Claim, Assumption, Finding, Evidence, Decision and
+Obligation projections and POO composition. These conceptual correspondences
+are not new implemented constructors or permission to add arbitrary edge names.
+POO Flow retains generic temporal/causal and query ownership. No parallel case
+graph engine, universal crawler, confidence scorer or MRR integration is
+required to qualify the first case.
+
+** First application and mechanism reuse
+
+[[file:../adrs/0008-stable-operation-key-after-lost-ack.org][ADR 0008]] applies this method to logical-operation identity. The AWS idempotency
+article supplies design questions, not evidence that Aitia or the fixture meets
+the AWS contract. The source/coverage matrix in that ADR distinguishes the
+existing replay from untested parameter, retention and crash boundaries.
+
+Authorization conditions belong in the existing POO Flow Cedar Provider, using
+trusted context supplied by the Host; do not create an Aitia policy evaluator.
+Cedar decides whether an action is allowed for the supplied principal, resource
+and context. Runtime must enforce that decision at use. Provider-side atomic
+deduplication and durable effects remain execution obligations: two concurrent
+requests can both be allowed without an execution-time uniqueness mechanism.
+An =Allow= does not establish that a previous attempt failed or did not commit.
+
+The current =assurance-host-cedar-request= and its currentness predicate are
+preflight projections, not a connected Runtime gate. Preserve their owner and
+reuse the existing effect-token machinery; qualify the missing handoff instead
+of treating the whole authorization design as absent. An operation identity
+survives retries, while a new attempt still requires the applicable current
+authorization. Outcome-unknown retries require provider reconciliation and
+idempotency guarantees, not an invented success or automatic fresh commit.
+
+* Reuse existing verification methods and engineering assets
+
+A Recipe is a composition of existing POO objects and slots describing
+applicability, bound inputs, operations, required checks, expected artifacts
+and failure handling. It is not a new configuration language or a separate
+workflow engine. RFC 0003 governs its maintained and advanced expression paths.
+
+Reuse existing functions, packages, scripts, models, harnesses and tool
+commands at a boundary that preserves their correctness conditions. The reuse
+unit includes the checking implementation, necessary dependencies, real
+execution path and relevant regression tests. When those pieces are coupled,
+first qualify the existing boundary or contribute a focused upstream change.
+Add a new implementation only for an identified capability gap.
+
+For every selected component or asset, record exact source revision/digest,
+original attribution and license, applicability assumptions, dependencies and
+the check establishing that it works in Aitia. The integration inventory must
+distinguish candidates from qualified reuse. Package layout alone does not
+establish a stable SDK or independent checker entry point.
+
+The maintained finite-state route is Quint with a pinned TLC backend; Lean
+retains its separate theorem lane. TLC state exploration and Lean theorem
+checking make different claims. Existing proof evidence kinds can name
+different producers without adding a parallel semantic schema for each tool.
+
+Specula and its case-study archive are candidate sources of verification tools,
+models, harnesses, traces and reproductions. The archive stores experiment
+records; each selected record needs a replay and attribution audit. The full
+Specula application is optional, not the required workflow owner. Calling an
+upstream script or bounded tool command is a legitimate reuse choice.
+Specula already provides continuous and incremental verification, so Aitia's
+proposed value must be demonstrated through design decisions, composition
+obligations and changing justification rather than that feature alone.
+
+* Runtime ownership and future MRR work
+
+The production Aitia Python Runtime under =bindings/python= serves all Aitia
+capabilities across the SDLC. AnyIO is its asynchronous foundation for task
+lifetime, cancellation, bounded concurrency and I/O. POO Flow continues to own
+generic Graph/FunFlow execution and temporal/causal algorithms. Scheme/POO owns
+Aitia's semantic composition, and the C ABI carries typed projections.
+Native session thread affinity must be preserved at asynchronous boundaries.
+Declaring the AnyIO dependency does not establish async execution or Host
+admission of a tool observation.
+
+MRR is deferred beyond the current Python implementation stage. A later design
+will evaluate relation queries, derivation and lineage over lifecycle
+snapshots; its role is broader than storing trace logs. Preserve identities,
+revisions, typed relations, modalities, cuts, witnesses and unknown frontiers
+now so a later integration can project them without redefining Aitia meaning.
+Do not add an MRR dependency, query adapter, storage framework or new Python
+inference engine in this phase. Future query results must retain snapshot and
+lineage bindings and pass the applicable admission boundary before use.
+
+* Qualification sequence and observable value
+
+The primary deliverable is one connected lifecycle experiment. The current
+job-executor case is a known-fault seed: per-key deduplication can succeed
+while a logical operation commits twice after acknowledgement loss. Its
+existing ADR 0008 and design-contract tests supply a starting point, not full
+causal or Host closure.
+
+1. Bind requirements for completion and unique logical effects, assumptions,
+ source, model when available, and at least two design alternatives.
+2. Replay the known failure through existing tools and preserve the actual
+ experimental trajectory. Record the proposed explanation and check its
+ relationship to the original per-key guarantee.
+3. Compare disabling retries with preserving a stable operation identity;
+ completion and provider retention assumptions remain explicit.
+4. Record the chosen revision and derive its obligations. Run the appropriate
+ reused tools, establish required source/model correspondence and consume
+ actual Host admission where a supported conclusion is claimed.
+5. Change a retention or retry assumption, or admit a late observation. Show
+ the new cut, affected guarantees, explanation paths and renewed obligations.
+6. Apply an implementation optimization and check both contract preservation
+ and measured benefit under the same workload and environment scope.
+
+Qualify one sufficiently complete upstream bug case as a reusable method.
+A known-answer replay is regression evidence; novel discovery needs a separate
+evaluation with unexposed answers.
+
+* Pending acceptance gates
+
+- [ ] One historical case records source attribution, applicability and
+ counterevidence, the design judgment it changed, and the resulting obligation.
+ Demonstrate a source correction reopening review without overwriting history.
+- [ ] ADR 0008's owner/coverage matrix is qualified, including the existing
+ Cedar/Runtime path and provider-side parameter, retention and crash boundaries;
+ documentation of a gap is not its executable closure.
+- [ ] Upstream reuse inventory binds selected files, revisions, dependencies,
+ licenses, required checks and replay evidence.
+- [ ] A maintained POO Composition selects the qualified Recipe and an
+ advanced override preserves mandatory checks or opens new obligations.
+- [ ] The connected lifecycle case compares design alternatives against the
+ same requirements, including safety and progress where applicable.
+- [ ] System, model, counterfactual and design trajectories preserve scope;
+ missing parents and late evidence have explicit negative cases.
+- [ ] A changed assumption yields attributable invalidation witnesses and
+ prevents stale support from being reused.
+- [ ] Introduction/change/retirement Impact binds pre/post snapshots, typed
+ relations and explicit temporal scope; proposal and observed effect remain
+ separate. Generic analysis stays in POO Flow.
+- [ ] The retirement case covers historical data/results, current consumers
+ and queued work; old-but-active subjects are not lost to past-event filtering.
+ Removed graph nodes cannot hide their old dependents. Unrelated controls are
+ not reopened, and incomplete external-consumer inventory remains unknown.
+- [ ] Historical validity, current applicability and later correction are
+ distinct; missing inventory and out-of-horizon effects cannot certify safe
+ deletion. Migration or compensation requires its own authorized receipts.
+- [ ] Frozen Python tool observations reach the actual Scheme Host admission
+ path; serialized success cannot substitute for that path.
+- [ ] The maintained POO contract's assumptions and guarantees have reviewed
+ Quint/Lean interpretations and falsifying controls, followed by concrete
+ Scheme/Runtime transition correspondence. Source identity alone is not
+ semantic refinement.
+- [ ] A contract-preserving optimization has conformance and performance
+ evidence, with no unreviewed weakening of the target requirement.
+- [ ] Compare the same upstream tools/assets with and without Aitia's design
+ and assurance links: check missed obligations, unnecessary reruns and the
+ ability to explain why a conclusion changed. Record limitations and costs.
+
+The upstream entry points, connected trajectory projection and Host bridge
+remain unqualified by this documentation change. Existing bounded primitives
+and their receipts retain their existing scope.
+
+* References
+
+- [[file:0001-causal-assurance-core.org][RFC 0001 — Causal Assurance Core]]
+- [[file:0002-snapshot-and-invalidation-kernel.org][RFC 0002 — Snapshot and Invalidation Kernel]]
+- [[file:0003-composition-value-interface.org][RFC 0003 — Composition Value Interface]]
+- [[file:0005-org-native-engineering-workspace.org][RFC 0005 — Org-native Engineering Workspace]]
+- [[file:../adrs/0006-production-python-sdlc-runtime.org][ADR 0006 — Aitia Python Runtime]]
+- [[file:../adrs/0008-stable-operation-key-after-lost-ack.org][ADR 0008 — Stable logical-operation identity]]
+- [[file:../../ROADMAP.org][Roadmap and current implementation evidence]]
+- [[file:../plans/engineering-case-source-inventory.org][Engineering case sources and transfer judgments]]
+- POO Flow =src/modules/temporal-causality/interface.ss= and architecture
+ =161-temporal-causality-and-causal-trajectory-architecture.org= own the generic
+ mechanism; Aitia consumes the pinned public library.
+- [[https://github.com/specula-org/Specula#continuous-verification][Specula continuous verification]]
+- [[https://github.com/specula-org/specula-case-studies][Specula experiment archive]]
+
+Upstream references were inspected on 2026-09-24. They identify reuse
+candidates, not pinned or qualified dependencies of this project.
diff --git a/docs/rfcs/0005-org-native-engineering-workspace.org b/docs/rfcs/0005-org-native-engineering-workspace.org
new file mode 100644
index 0000000..7dbfccb
--- /dev/null
+++ b/docs/rfcs/0005-org-native-engineering-workspace.org
@@ -0,0 +1,361 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: RFC 0005 — Org-native engineering workspace
+#+startup: overview
+
+:PROPERTIES:
+:ID: lambda-aitia-rfc-0005
+:STATUS: draft
+:DATE: 2026-09-24
+:PARENT_RFC: lambda-aitia-rfc-0004
+:IMPLEMENTS: lambda-aitia-rfc-0004
+:END:
+
+* Decision and ownership
+
+Native Org is a maintained engineering work surface, not a Markdown document
+decorated with execution commands. Requirements, assumptions, alternatives,
+review work and receipts remain addressable parts of the same design history.
+Orgize owns syntax, lossless structure and semantic queries. POO Flow owns
+generic composition, organization, causal/temporal mechanisms and the reusable
+Org integration. Aitia owns Software/SDLC interpretations and workspaces.
+Existing Cedar and Runtime mechanisms retain effect authority and execution.
+
+The agreed dependency owner is POO Flow. The target is for its =gerbil.pkg=
+to declare a qualified Orgize Scheme package once; the current source graph
+cannot support that edge. Rust/Rowan runtime delivery is a long-term runtime
+direction, not an interim substitute for this package target and not the
+current Python milestone. Aitia, Lambda
+and other POO Flow consumers must not each acquire an independent Org parser
+or duplicate its package pin.
+Package availability does not mean implicit Scheme symbol imports: a public
+POO Flow module/composition still controls what consumers receive.
+
+The C ABI is a transport, not a new semantic owner. Orgize owns Org language
+semantics, and its Scheme/Gambit package owns the generic Org C ABI. POO Flow
+inherits Orgize once and exposes the POO-native Org facade;
+its existing downstream native boundary may forward qualified Org operations
+without redefining their semantics. Aitia depends on POO Flow and does not
+invent a separate Org wire protocol or pin Orgize independently. Rust can call
+the Orgize C ABI when it needs that Scheme runtime boundary, while native Rust
+consumers can continue using the Scheme-generated AOT library API directly.
+Neither path makes the other an implicit fallback or a second authority.
+
+The first maintained surface is [[file:../../user-interface/workspaces/job-executor.org][the job-executor workspace]], connected to
+[[file:../adrs/0008-stable-operation-key-after-lost-ack.org][ADR 0008]]. Its [[file:../../user-interface/contracts/engineering-workspace.org][authoring contract]] uses Orgize's existing =org-contract=
+implementation, not an Aitia parser or evaluator.
+
+** Scheme source authority and Rowan delivery
+
+The selected upstream surface is =scheme :org-elements= and
+=scheme :org-contract=. Org properties hold identity and scope metadata;
+the block markers select bounded Scheme declarations. Org Elements supplies
+POO query values and selection semantics. Org Contract supplies POO assertion
+values and evaluation semantics. Its hygienic forms admit the declarations
+before the upstream Scheme generators emit Rust AOT plans.
+
+Rust consumes these plans over the Scheme-declared lossless Rowan graph.
+The public =parse_org_aot= and generated query/contract packs are the consumer
+path. A legacy =orgize lint= or =contract trace= executable is not evidence
+that this path works, even if its source checkout is on the correct branch.
+Do not build a Python evaluator or translate the old contract syntax into a
+second authority. Our contracts use the upstream Scheme forms directly.
+
+** Implementation horizons
+
+The current executable milestone is Aitia's production, uv-managed Python
+Runtime. It consumes the existing Scheme/C ABI, runs real verification tools
+with AnyIO-owned task lifetimes, and must carry frozen source-bound observations
+to the actual Scheme Host admission path. For this Org workspace, Python may
+coordinate an upstream-qualified Org query/contract result as an observation;
+it must not parse Org independently, evaluate =org-contract= independently,
+or treat a successful CLI exit as admission. Org-specific Python consumption
+remains pending until its upstream result and source identity can be conveyed
+through a qualified boundary.
+
+The current Orgize PR has Scheme declarations and generated Rust/Rowan
+consumer APIs, but no =gerbil.pkg=/=build.ss= Scheme package entrypoint,
+Scheme-exported =c-define= surface, or installed shared-library contract.
+Its Rust =org_aot_edit::apply_org_source_edits= is a typed library function,
+not that ABI. Aitia's existing Scheme/Gambit C ABI proves the mechanism is
+available locally, but does not make Orgize's semantics available through it.
+The initial Scheme ABI must be scoped to semantics that Scheme actually runs
+over admitted graph values, such as Element selection and Contract evaluation;
+do not label it a source-parser ABI until source-to-graph execution and its
+ownership, errors and lifetime are implemented and tested through that path.
+
+The later Rust/Rowan runtime is the intended durable execution path for
+Scheme-generated Org artifacts. Its source, generated packs, Rust consumer,
+the Scheme-owned ABI and POO Flow facade require their own qualification. Its direction
+does not make a Rust FFI build a prerequisite for the current Python/Host work.
+The Scheme package-cycle audit below is a separate dependency-design gate;
+neither postponing it nor qualifying Rust alone satisfies the requested
+=gerbil.pkg= inheritance.
+
+* Native syntax and proposed semantic projection
+
+| Native Org element | Aitia interpretation | Boundary |
+|--------------------+----------------------+----------|
+| Heading and =ID= property | addressable design or review node | title and nesting do not grant identity authority |
+| Property drawer | revision, kind, owner-role and subject references | custom properties below are proposed conventions |
+| TODO keyword and checkboxes | authoring/review work state | DONE is neither assurance support nor authorization |
+| Links and tables | source references and alternative comparison | a link does not admit evidence |
+| LOGBOOK | attributed design-revision narrative | not a substitute for trusted execution receipts |
+| Source/export block | typed content owned by its language/backend | parsing or rendering must not execute it |
+
+=AITIA_KIND=, =OWNER_ROLE= and =SUBJECT_ID= are project conventions in this
+draft, not existing Orgize built-ins or registered POO principal identities.
+Resolve them through an explicit, validated POO-native projection. Reject
+duplicate IDs and unresolved required references instead of guessing a role
+from the heading hierarchy. User-facing defaults belong to the maintained
+Composition; advanced consumers may extend its POO slots.
+
+* Agent collaboration without ambient authority
+
+A role-scoped context contains the selected design node, relevant assumptions,
+alternatives, contrary material and evidence bounds. Organization Bundle
+identities and delegated visibility must be resolved before an Agent runs.
+No heading, TODO transition or =OWNER_ROLE= declaration spawns an Agent.
+
+An edit proposal binds the source digest, stable node ID, original source range
+and expected old content. The owner reviews the proposal before application.
+Any digest/content conflict requires re-query and review; never patch a stale
+offset. Preserve unrelated text, property drawers and comments through Orgize's
+source-owned representation. A new design revision reopens the affected
+obligations through existing POO Flow causal mechanisms, not a parallel graph.
+
+Design status, scoped evidence support, authorization and observed execution
+remain independent. Agent review cannot certify its own external guarantees.
+Historical cases may inspire a judgment; only qualified checks support a claim.
+
+#+begin_export mermaid
+flowchart TD
+ A["Org source
authored"] --> Q["Orgize projection
source-bound"]
+ Q --> R["Role-scoped review
proposed"]
+ R --> P["Edit proposal
owner review pending"]
+ P --> C{"Source still matches?"}
+ C -->|no| Q
+ C -->|yes, accepted| D["Design revision
obligations reopened"]
+ D --> V["Verification
scoped evidence pending"]
+ V --> G["Cedar / Runtime admission
effect authority pending"]
+ G --> E["Execution
receipt observed"]
+ E --> A
+#+end_export
+
+The diagram specifies intended interactions. The authoring/query checks below
+do not qualify this complete loop.
+
+* Package boundary audit — blocking direct installation
+
+At the first 2026-09-24 audit, the selected Orgize branch was
+=codex/scheme-rowan-org-cutover= at
+=4a94883bd47196a175a88f552410d59102f34d24=. Its Scheme definitions live under
+=languages/org/v1/=; =modules/org-elements/interface.ss= exports POO query
+objects and functions. The branch has no root =gerbil.pkg= or =build.ss=.
+Do not infer an installable Gerbil package from the presence of these sources.
+
+The fetched Orgize branch now includes consumer-owned Contract import
+generation at =5cc02a1c5f882a3b182461ce803b8c76c4772c36= and pins
+gerbil-parser to =ca01627c0c5ba1d460c0de83b060bc90d3b961e8=. That
+parser revision's Scheme =gerbil.pkg= still depends on POO Flow at
+=2ce55a9810f8559cc0f40a6c877a52ab7893c166=. In particular,
+=graph-projection-objects.ss= and =line-structure-objects.ss= use POO Flow's
+contract admission and object-family declarations. This is an actual source
+dependency, not just a stale manifest entry.
+
+Adding a source-package edge now would produce:
+
+#+begin_export mermaid
+flowchart LR
+ P["POO Flow"] -. "requested, not installed" .-> O["Orgize Scheme package
entrypoint missing"]
+ O -->|parser declarations| G["gerbil-parser"]
+ G -->|contracts and object families| P
+#+end_export
+
+The integration must first establish an acyclic build/runtime package boundary.
+Two options require review: separate generator dependencies from the delivered
+Org runtime package, or relocate genuinely generic shared foundations below
+both packages. Do not copy POO contracts into Orgize, silently delete parser
+dependencies, or maintain a second parser. This RFC does not authorize or claim
+completion of that cross-repository restructuring.
+
+This cycle concerns installing the Scheme source-package graph. It does not
+describe the existing Cargo runtime graph: committed Scheme-generated AOT
+artifacts are already consumed by Rust without a Gerbil runtime dependency.
+Do not block qualification of that existing consumer path on a hypothetical
+source-package installation, or conflate generator dependencies with delivery.
+
+After a reviewed boundary is qualified, pin the Orgize Scheme dependency in
+POO Flow, expose a public unversioned consumer interface, and verify a clean
+downstream install using only POO Flow. Qualification of the later Rust runtime
+is independent of this source-package gate. Internal grammar versions are not
+consumer namespace versions.
+
+** Source-package cycle cut and qualification order
+
+The requested Scheme source-package direction would be lower-level POO
+primitives → =gerbil-parser= → Orgize → POO Flow → Aitia. At the selected
+Orgize cut, =org-elements/types.ss= imports parser-owned graph projection
+objects. The parser has seven Scheme modules importing POO Flow. These imports
+are not merely incidental helpers: they use =PooFlowContract.= and
+=PooFlowNativeObjectContract.=, evidence-bearing validation and admission,
+=defpoo-object-family=, and the POO Flow debug trace. Replacing them with bare
+=clan/poo= would change semantics; moving or copying those facilities below
+POO Flow is not an approved mechanical refactor.
+
+| Parser source modules | POO Flow-owned dependency | Required observation to retain |
+|-----------------------+---------------------------+--------------------------------|
+| =types.ss=, =graph-projection-types.ss=, =line-structure-types.ss= | contract prototypes, classification and evidence admission | typed values and rejected candidates keep their current identities and failure semantics |
+| =objects.ss=, =graph-projection-objects.ss=, =line-structure-objects.ss= | object-family declaration and evidence-gated constructors | public POO prototypes, slots and constructor rejection remain unchanged |
+| =runtime/observability.ss= | debug trace policy and emission | disabled/enabled phase behavior and receipt shape remain unchanged |
+
+This is a source-level inventory, not a test receipt proving equivalence after
+a refactor. The exact consumer graph and all negative cases must be captured
+before moving ownership.
+
+These are distinct future engineering tracks, neither yet delivered:
+
+1. *Rust/Rowan runtime delivery (long-term target, not current Python work).* Keep
+ Orgize's Scheme declarations and generators in their current source/tooling
+ lane. Package the Scheme-generated Rust AOT consumer as an immutable,
+ independently consumable runtime. It may consume a qualified Scheme C ABI
+ where that is the right runtime boundary, or expose its own native Rust
+ library API; do not require an Orgize-Rust-owned C ABI merely to serve
+ Aitia Python. Orgize already exposes =parse_org_aot=, Element records,
+ source receipts, query and contract evaluation as Rust library APIs, but
+ this is not yet a C ABI, a Gerbil package, or an installed POO Flow
+ dependency. POO Flow owns the POO-native facade and admission; it must call
+ upstream semantics, not reimplement Org queries or contracts.
+ Pin generator and runtime revisions/digests together and qualify a clean
+ binary/runtime install. This can supply a durable POO Flow-owned runtime
+ dependency for downstreams, but it does *not* fulfill the separate request
+ that Orgize Scheme source be inherited through =gerbil.pkg=.
+2. *Scheme source-graph refactor (the stated package target).* Keep the desired
+ source-package inheritance only after resolving parser → POO Flow without
+ losing its contract/evidence, object-family and observability behavior.
+ An existing lower library may own genuinely generic mechanisms only if
+ authority and public observations move together; otherwise the dependency
+ direction must be redesigned. Do not fork these semantics, create a second
+ AST, or add compatibility forwarding simply to make installation pass.
+
+Qualify the stated Scheme package target in this order:
+
+1. Lock positive and negative observations for the seven parser imports,
+ including contract admission, object-family behavior and debug traces.
+2. Review the actual authority-preserving source-graph cut. Only after it is
+ accepted, remove parser → POO Flow and replay parser/compiler/runtime tests
+ from a clean dependency closure.
+3. Add Orgize's source-owned Gerbil package/build entrypoint and qualify its
+ Scheme Elements/Contract declarations and Rowan/AOT generation against
+ that parser revision.
+4. Pin Orgize once in POO Flow's =gerbil.pkg=, expose its POO-native unversioned
+ Org interface, and replay an Aitia workspace with POO Flow as its sole
+ direct Org dependency. Prove clean installation without ambient
+ =GERBIL_LOADPATH= or an old compiled module.
+
+When the later Rust runtime track begins, qualify exact generated
+pack/source/parser identity and stale-pack rejection first; then define an
+upstream-owned stable FFI with explicit ownership, errors and memory limits.
+A Rust =pub fn= or prebuilt executable alone is not that boundary. That runtime
+must not be relabeled as Scheme source-package closure. The existing =4a94883=
+Cargo/AOT checks remain independent evidence, not evidence that either
+package or FFI gate has closed. RFC 0005 stays draft until those gates pass.
+
+* Acceptance and evidence scope
+
+1. Authoring: upstream Rowan/CST tangling reads the native Org metadata and
+ tagged Scheme declarations. Scheme admission and AOT generation qualify the
+ six document and five decision-local assertions. No legacy CLI substitution.
+2. Query/evaluation: run those generated plans over the actual workspace and
+ negative fixtures. Missing content, a single alternative, content under
+ another decision and a receipt link in the wrong section must fail the
+ intended assertion. This checks structure, not evidence truth or authority.
+3. Package: clean, acyclic Orgize Scheme source → POO Flow inheritance;
+ downstreams declare only POO Flow. The later Rust/Rowan runtime has a
+ separate delivery gate. Both remain open, neither blocks the present
+ Python/Host admission work.
+4. Projection: POO-native identity/reference/role resolution plus negative
+ duplicate, missing-reference and unauthorized-visibility tests. Pending.
+5. Mutation: Aitia Python rejects moved spans, stale source digests and
+ overlapping proposals before owner review. Orgize PR #15 now has a
+ Scheme-AOT-graph-backed candidate source transformation that checks unique
+ node =:ID:= ownership, rejects wrong-node spans and preserves unrelated
+ bytes. The transport from Aitia Python to that API and reviewed persistence
+ are still pending; neither preflight nor a candidate is authorization.
+6. Lifecycle: an accepted edit reopens an actual obligation, a real tool runs
+ through existing admission, and its scoped receipt feeds a design review.
+ Pending; a TODO transition or successful lint is not this receipt.
+
+** Correction of the initial receipt — 2026-09-24
+
+The earlier authoring receipt used an installed CLI at
+=52d315155ebbba917f0694068444d3bbde5f9920=, not the selected Scheme/Rowan
+consumer path. Its six passing assertions and task queries do not qualify
+this integration. The later cross-decision negative tests on that old path
+also failed. Neither result establishes behavior of the new Scheme modules.
+The legacy CLI recipe and its Python qualification test have been withdrawn;
+the contract source now uses upstream Scheme declarations.
+
+The earlier fetch/rebase confirmed that =codex/scheme-rowan-org-cutover= and
+its remote were both at =4a94883bd47196a175a88f552410d59102f34d24= at
+that time. The mistake was the
+consumer path, not an unrebased HEAD. New evidence must identify the source
+revision, generated artifact and actual consumer entrypoint separately.
+
+** Correct-path bounded receipt — 2026-09-24
+
+At the fetched/rebased =4a94883= revision, the following upstream checks passed
+using locked, offline Cargo dependencies and two build workers:
+
+| Check | Result | Scope |
+|-------+--------+-------|
+| =integration_test org_parser_aot= | 21 passed | Scheme-generated parser, Rowan graph and existing generated contract execution |
+| =integration_test org_element_query= | 3 passed | generated queries, consumer pack, scope and fail-closed checks |
+| =--example org_contract_tangle= | 5 passed | Scheme block extraction, metadata and ancestry rejection |
+| =--example org_elements_tangle= | 5 passed | Scheme query extraction and existing consumer source/artifact consistency |
+| Upstream =org_contract_tangle= on Aitia's registry | 2 definitions / 11 assertions extracted | actual Org CST to Scheme source, not Scheme admission or AOT execution |
+
+The first build selected Nix's =cc= and failed to link =iconv=. Retrying with
+process-local =CC=/usr/bin/cc= and
+=CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=/usr/bin/cc= passed; no global
+environment or toolchain configuration was changed. Commands also unset
+=SDKROOT= for that process and set =CARGO_BUILD_JOBS= to 2.
+
+At that earlier cut, the extracted Aitia source was temporary and had not
+been compiled/evaluated: the extractor emitted upstream-relative interface
+imports. Consumer-owned module location, Scheme admission, AOT generation and
+cross-decision negatives were still required. The 34 upstream passes did not
+constitute Aitia end-to-end completion.
+
+** Consumer-owned Scheme source and AOT plan slice — 2026-09-24
+
+At the later fetched Orgize =5cc02a1= cut, =org_contract_tangle= accepts an
+optional Contract and Element interface pair. The default output remains
+byte-for-byte equal to upstream's committed generated Scheme file. Six
+generator tests pass, including empty-interface rejection and consumer-owned
+imports. Running that generator on Aitia's actual
+=engineering-workspace.org= yielded two definitions and eleven assertions.
+Gerbil then constructed these declarations and the upstream Scheme AOT
+generator emitted a plan with the current Org graph projection digest
+=sha256:f26d350a0456114b37a9af347b5b4694a9eff8dd787df988296814f1a60f29a8=.
+
+This is a bounded source/plan result, not actual Aitia workspace evaluation.
+The isolated invocation still emitted a POO Flow observability warning from
+mixed local Gerbil objects. A clean package closure and consumer-side Rust
+evaluation with cross-decision negatives remain unqualified. The direct
+Orgize source-package cycle described above is unchanged; do not promote the
+generated plan or this warning-bearing local check to a published interface.
+
+** Source-owned mutation implementation on Orgize PR #15 — 2026-09-24
+
+[[https://github.com/tao3k/orgize/pull/15][Orgize PR #15]] now includes commit =54de4fe=, which adds
+=org_aot_edit::apply_org_source_edits=. It consumes the Scheme-AOT Element
+graph, validates exact source digest, unique headline =:ID:=, UTF-8 byte
+range, expected old text and non-overlap, then reparses the candidate source.
+It does not call the old =Org::replace_range= path or write a file. Local
+integration tests passed 367/367 with two ignored; focused mutation tests
+passed 4/4, and strict Clippy plus formatting passed. This closes the
+Orgize-owned candidate transformation, not Aitia's transport or an
+owner-approved persistent edit. The PR itself remains draft for the broader
+parser cutover and dependency gates.
diff --git a/justfile b/justfile
index 81c7d0e..c56e4dc 100644
--- a/justfile
+++ b/justfile
@@ -11,6 +11,7 @@ export GERBIL_PATH := env_var_or_default("GERBIL_PATH", self_root + "/.gerbil")
export GERBIL_LOADPATH := env_var_or_default("GERBIL_LOADPATH", self_root + ":" + GERBIL_PATH + "/lib")
native_root := GERBIL_PATH + "/native"
native_library := native_root + "/libpoo_flow_aitia.dylib"
+python_native_dir := self_root + "/bindings/python/src/lambda_aitia/_native"
gerbil_env := if os() == "macos" { "env -u SDKROOT" } else { "env" }
export UV_CACHE_DIR := env_var_or_default("UV_CACHE_DIR", self_root + "/.cache/uv")
darwin_cc := if os() == "macos" { "/usr/bin/cc" } else { "cc" }
@@ -62,7 +63,50 @@ check-semantic:
# Build the Aitia-owned assurance laws independently of POO Flow's proof package.
[group('check')]
check-proof:
- cd proof/lean && lake build LambdaAitiaModuleAssuranceProof
+ cd proof/lean && lake build LambdaAitiaModuleAssuranceProof LambdaAitiaModuleSdlcProof
+ cd proof/lean && output="$(lake env lean AxiomAudit.lean)" && printf '%s\n' "$output" && ! grep -q 'sorryAx' <<< "$output"
+
+# Quint 0.33.0 and its Apalache 0.62.1 TLC backend are explicit inputs.
+# The checker exhausts each bounded model and requires negative-control traces.
+[group('check')]
+check-quint module='all':
+ python3 proof/quint/check_contract.py '{{module}}'
+
+# Pinned Quint MBT traces, native Scheme execution, and explicit fault refusals.
+[group('check')]
+check-quint-scheme:
+ python3 proof/quint/check_scheme_replay.py
+
+# Complete reachable lifecycle states, named actions, and fault refusals.
+[group('check')]
+check-lifecycle-scheme: build-scheme
+ python3 proof/quint/test_lifecycle_replay.py
+ python3 proof/quint/check_lifecycle_replay.py
+
+# Every gate must run; none substitutes for semantic implementation refinement.
+[group('check')]
+check-formal:
+ just test-scheme-atomic formal retirement-binding-test.ss
+ just test-scheme-atomic formal retirement-execution-test.ss
+ just check-quint-scheme
+ just check-lifecycle-scheme
+ just check-quint
+ just check-proof
+
+# Refresh the in-place Python extension without rebuilding POO Flow.
+[group('dev')]
+dev-python:
+ test -f '{{ GERBIL_PATH }}/pkg/github.com/tao3k/orgize/bindings/c/include/orgize.h' || { echo 'Orgize C header missing from GERBIL_PATH; install the declared POO Flow Gerbil dependencies first' >&2; exit 1; }
+ just clean-python-cffi
+ cd bindings/python && {{ gerbil_env }} uv sync --locked --extra test
+ cd bindings/python && {{ gerbil_env }} uv run --locked --extra test python src/lambda_aitia/_native/_build.py
+ cd bindings/python && {{ gerbil_env }} uv run --locked --extra test python -c 'from lambda_aitia._native._aitia_cffi import ffi; ffi.typeof("orgize_element_row"); print("aitia-cffi-current")'
+
+# Remove only generated CFFI artifacts, not Python sources or the uv venv.
+[group('clean')]
+clean-python-cffi:
+ find '{{ python_native_dir }}' -maxdepth 1 -type f -name '_aitia_cffi.*' -delete
+ if test -d '{{ python_native_dir }}/_build_temp'; then find '{{ python_native_dir }}/_build_temp' -depth -delete; fi
# Aitia owns its native ABI and Python Runtime checks.
[group('check')]
@@ -77,9 +121,16 @@ check-native:
'{{ native_root }}/aitia_dynamic_harness' '{{ native_library }}'
just test-scheme bindings
cd bindings/python && {{ gerbil_env }} uv lock --check
+ just clean-python-cffi
cd bindings/python && {{ gerbil_env }} uv run --locked --extra test python src/lambda_aitia/_native/_build.py
cd bindings/python && LAMBDA_AITIA_NATIVE_LIBRARY='{{ native_library }}' {{ gerbil_env }} uv run --locked --extra test pytest
just build-python-wheel '{{ native_library }}'
+ just check-installed-wheel
+
+# Exercise the delivered wheel from a fresh environment outside the checkout.
+[group('check')]
+check-installed-wheel:
+ cd bindings/python && {{ gerbil_env }} uv run --locked --extra test pytest tests/installed_wheel_qualification.py
# Build the production wheel from Aitia's locked uv environment. The preceding
# sync makes the declared PEP 517 backend available without a second isolated
@@ -87,12 +138,12 @@ check-native:
[group('build')]
build-python-wheel native_library:
test -f '{{ native_library }}'
- cd bindings/python && LAMBDA_AITIA_NATIVE_LIBRARY='{{ native_library }}' env -u SDKROOT uv sync --locked --extra test
- cd bindings/python && LAMBDA_AITIA_NATIVE_LIBRARY='{{ native_library }}' env -u SDKROOT uv build --wheel --offline --no-build-isolation
+ cd bindings/python && LAMBDA_AITIA_NATIVE_LIBRARY='{{ native_library }}' {{ gerbil_env }} uv sync --locked --extra test
+ cd bindings/python && LAMBDA_AITIA_NATIVE_LIBRARY='{{ native_library }}' {{ gerbil_env }} uv build --wheel --offline --no-build-isolation
# Current repository closure. No recipe grants runtime authority.
[group('check')]
-check: check-license-contract check-semantic check-proof test-integration check-native
+check: check-license-contract check-semantic check-proof check-quint test-integration check-native
[group('check')]
check-all: check
diff --git a/modules/ADR/config.ss b/modules/ADR/config.ss
index c4d5244..99225cf 100644
--- a/modules/ADR/config.ss
+++ b/modules/ADR/config.ss
@@ -3,7 +3,7 @@
;;;
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
-(import :poo-flow/src/module-system/declaration/interface
+(import :poo-flow/src/user-interface/module-selection
:poo-flow/lambda-aitia/modules/ADR/funs)
(export ADR-config)
diff --git a/modules/ADR/funs.ss b/modules/ADR/funs.ss
index 55bbb17..d6f6cd1 100644
--- a/modules/ADR/funs.ss
+++ b/modules/ADR/funs.ss
@@ -4,7 +4,7 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import (only-in :clan/poo/object .ref)
- (only-in :poo-flow/src/modules/governance/funs
+ (only-in :poo-flow/modules/governance/funs
poo-flow-governance-contribution)
:poo-flow/lambda-aitia/modules/ADR/types
:poo-flow/lambda-aitia/modules/ADR/objects
diff --git a/modules/ADR/objects.ss b/modules/ADR/objects.ss
index 8769ec3..7bf9275 100644
--- a/modules/ADR/objects.ss
+++ b/modules/ADR/objects.ss
@@ -4,8 +4,8 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import (only-in :clan/poo/object .o)
- :poo-flow/src/module-system/contribution/model
- (only-in :poo-flow/src/modules/governance/objects
+ :poo-flow/core/poo-clos/interface
+ (only-in :poo-flow/modules/governance/objects
PooFlowGovernanceProfile.
poo-flow-governance-source)
:poo-flow/lambda-aitia/modules/ADR/types)
@@ -32,24 +32,24 @@
"modules/ADR/expired-references.gql" 'gql))))
(def (ADR-link relation-value target-value)
- (poo-flow-check-model
+ (poo-clos-check-model
ADRLink
- (.o (:: @ (poo-flow-model-prototype ADRLink))
+ (.o (:: @ (poo-clos-model-prototype ADRLink))
relation: relation-value target: target-value)))
(def (ADR-option identity-value title-value description-value
disposition-value pros-value cons-value)
- (poo-flow-check-model
+ (poo-clos-check-model
ADROption
- (.o (:: @ (poo-flow-model-prototype ADROption))
+ (.o (:: @ (poo-clos-model-prototype ADROption))
identity: identity-value title: title-value
description: description-value disposition: disposition-value
pros: pros-value cons: cons-value)))
(def (ADR-consequence sentiment-value statement-value)
- (poo-flow-check-model
+ (poo-clos-check-model
ADRConsequence
- (.o (:: @ (poo-flow-model-prototype ADRConsequence))
+ (.o (:: @ (poo-clos-model-prototype ADRConsequence))
sentiment: sentiment-value statement: statement-value)))
(def (ADR-record identity-value title-value status-value date-value
@@ -58,9 +58,9 @@
confirmation-value decision-makers-value consulted-value
informed-value links-value)
(let ((record
- (poo-flow-check-model
+ (poo-clos-check-model
ADRRecord
- (.o (:: @ (poo-flow-model-prototype ADRRecord))
+ (.o (:: @ (poo-clos-model-prototype ADRRecord))
identity: identity-value title: title-value status: status-value
date: date-value revision: revision-value context: context-value
decision: decision-value decision-drivers: drivers-value
diff --git a/modules/ADR/types.ss b/modules/ADR/types.ss
index 7e94194..bf76c4f 100644
--- a/modules/ADR/types.ss
+++ b/modules/ADR/types.ss
@@ -5,8 +5,8 @@
(import (only-in :clan/poo/object .ref .slot?)
:std/list/list
- :poo-flow/src/module-system/contribution/model
- (only-in :poo-flow/src/modules/governance/types
+ :poo-flow/core/poo-clos/interface
+ (only-in :poo-flow/modules/governance/types
poo-flow-governance-profile?))
(export ADR-profile?
@@ -95,10 +95,10 @@
(predicate-slot 'informed ADR-text-list?)
(predicate-slot 'links ADR-link-list?))))
-(def (ADR-link? value) (poo-flow-model? ADRLink value))
-(def (ADR-option? value) (poo-flow-model? ADROption value))
-(def (ADR-consequence? value) (poo-flow-model? ADRConsequence value))
-(def (ADR-record? value) (poo-flow-model? ADRRecord value))
+(def (ADR-link? value) (poo-clos-model? ADRLink value))
+(def (ADR-option? value) (poo-clos-model? ADROption value))
+(def (ADR-consequence? value) (poo-clos-model? ADRConsequence value))
+(def (ADR-record? value) (poo-clos-model? ADRRecord value))
(def (unique-by? projection input-values)
(length=? input-values
diff --git a/modules/assurance/config.ss b/modules/assurance/config.ss
index b9e2d69..4960c6f 100644
--- a/modules/assurance/config.ss
+++ b/modules/assurance/config.ss
@@ -4,9 +4,12 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import (only-in :clan/poo/object .o)
+ (only-in :gerbil/core list-sort)
+ :std/list/list
+ :poo-flow/core/poo-clos/interface
:poo-flow/lambda-aitia/modules/assurance/types)
-(export AssurancePolicy)
+(export AssurancePolicy assurance-verification-policy)
;;; Pure declared defaults. This value invokes no verifier and grants no
;;; authority.
@@ -18,3 +21,18 @@
conflict-resolution: 'explicit
planner-executes?: #f
grants-authority?: #f))
+
+(def (capability left right)
+ (string (symbol->string left) (symbol->string right)))
+
+;;; Capabilities describe available adapters; this value cannot invoke one.
+(def (assurance-verification-policy identity-value revision-value capability-values)
+ (unless (and (list? capability-values) (every symbol? capability-values))
+ (error "verification policy capabilities must be symbols" capability-values))
+ (poo-clos-check-model
+ AssuranceVerificationPolicy
+ (.o (:: @ (poo-clos-model-prototype AssuranceVerificationPolicy))
+ identity: identity-value revision: revision-value
+ capabilities:
+ (list-sort capability (delete-duplicates/hash capability-values))
+ planner-executes?: #f grants-authority?: #f)))
diff --git a/modules/assurance/effect-host.ss b/modules/assurance/effect-host.ss
new file mode 100644
index 0000000..d8a51b6
--- /dev/null
+++ b/modules/assurance/effect-host.ss
@@ -0,0 +1,144 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; Local serialized integration boundary, not a Cedar evaluator or distributed
+;;; Runtime Host. Only configured runtime operations evaluate policy / deliver.
+;;; Editable preflights, requests, tokens and reports never issue capabilities.
+(import (only-in :clan/poo/object .o .ref)
+ :poo-flow/lambda-aitia/modules/assurance/host
+ :poo-flow/src/policy/authorized-effect-token
+ (only-in :poo-flow/modules/authorization/providers/cedar/decision
+ poo-flow-cedar-decision-permit?
+ poo-flow-cedar-decision->semantic-root
+ poo-flow-cedar-decision->authorized-effect-token))
+(export assurance-effect-host assurance-effect-host-epoch
+ assurance-effect-host-authorize! assurance-effect-host-grant-current?
+ assurance-effect-host-reserve! assurance-effect-host-deliver!
+ assurance-effect-host-revoke!)
+
+(def hosts (make-hash-table-eq weak-keys: #t))
+(def grants (make-hash-table-eq weak-keys: #t))
+(def reservations (make-hash-table-eq weak-keys: #t))
+
+(def (entry host)
+ (let (state (hash-get hosts host))
+ (unless (and state (eq? (vector-ref state 0) (current-thread)))
+ (error "effect Host requires its exact serialized owner"))
+ (when (vector-ref state 7) (error "effect Host mutation during runtime handoff"))
+ state))
+(def (instant state)
+ (let (now ((vector-ref state 3)))
+ (unless (and (exact-integer? now) (>= now (vector-ref state 8)))
+ (error "effect Host clock must be monotonic and nonnegative"))
+ (vector-set! state 8 now)
+ now))
+(def (handoff state operation)
+ (dynamic-wind
+ (lambda () (vector-set! state 7 #t))
+ operation
+ (lambda () (vector-set! state 7 #f))))
+
+;;; Runtime authorize: Request -> CedarDecision or #f (deny/indeterminate).
+;;; Runtime deliver: Request Token -> delivered / failed / indeterminate.
+;;; 'delivered is an attributable acknowledgement, not inferred from start.
+(def (assurance-effect-host authorize deliver clock lease)
+ (unless (and (procedure? authorize) (procedure? deliver) (procedure? clock)
+ (exact-integer? lease) (> lease 0))
+ (error "invalid effect Host runtime configuration"))
+ (let (host (.o kind: 'aitia.effect-host.v1))
+ (hash-put! hosts host (vector (current-thread) authorize deliver clock lease 0 0 #f 0))
+ host))
+(def (assurance-effect-host-epoch host) (vector-ref (entry host) 5))
+(def (assurance-effect-host-revoke! host)
+ (let (state (entry host))
+ (vector-set! state 5 (+ 1 (vector-ref state 5)))))
+
+(def (grant-entry host grant)
+ (let (issued (hash-get grants grant))
+ (and issued (eq? host (vector-ref issued 0))
+ (equal? (.ref grant 'expires-at) (vector-ref issued 5))
+ (equal? (.ref grant 'epoch) (vector-ref issued 4))
+ (equal? (poo-flow-authorized-effect-token-digest (.ref grant 'token))
+ (vector-ref issued 6)) issued)))
+(def (current? state issued)
+ (and issued
+ (= (vector-ref state 5) (vector-ref issued 4))
+ (< (instant state) (vector-ref issued 5))
+ (assurance-host-cedar-request-current? (vector-ref issued 1) (vector-ref issued 2))
+ ;; Source validation may take time. Apply strict expiry to the same
+ ;; final instant passed to the token validator, whose endpoint is
+ ;; otherwise inclusive; an earlier clock sample must not authorize use.
+ (let (now (instant state))
+ (and (< now (vector-ref issued 5))
+ (.ref (poo-flow-authorized-effect-token-validate
+ (vector-ref issued 3)
+ (poo-flow-token-validation-context
+ (.ref (vector-ref issued 3) 'semantic-root)
+ (.ref (vector-ref issued 3) 'binding)
+ now (vector-ref state 5) '())) 'accepted?)))))
+(def (assurance-effect-host-grant-current? host grant)
+ (let (state (entry host))
+ (and (current? state (grant-entry host grant)) #t)))
+
+(def (assurance-effect-host-authorize! host verification-host request)
+ (let* ((state (entry host)) (now (instant state)) (epoch (vector-ref state 5)))
+ (and (assurance-host-cedar-request-current? verification-host request)
+ (let (decision (handoff state (lambda () ((vector-ref state 1) request))))
+ (and decision (poo-flow-cedar-decision-permit? decision)
+ (= epoch (vector-ref state 5))
+ (< (instant state) (+ now (vector-ref state 4)))
+ (assurance-host-cedar-request-current? verification-host request)
+ (let* ((next (+ 1 (vector-ref state 6)))
+ (context (.ref request 'context))
+ (bundle (.ref context 'aitia_snapshot_semantic_digest))
+ (intent (.ref request 'intent))
+ (root (poo-flow-cedar-decision->semantic-root decision bundle intent))
+ (binding
+ (poo-flow-effect-binding
+ bundle epoch (.ref decision 'policy-digest) (.ref decision 'entity-digest)
+ (.ref decision 'decision-digest) intent next (.ref request 'action)
+ 'configured-runtime 'local 1 1 'local 0 0 1 intent 'local))
+ (until (+ now (vector-ref state 4)))
+ (token (poo-flow-cedar-decision->authorized-effect-token
+ decision next next root binding
+ (poo-flow-token-validity now now until epoch)
+ 'strict 1 'configured-runtime "local-adapter"))
+ (grant (let ((token-value token) (epoch-value epoch))
+ (.o kind: 'aitia.effect-grant.v1 token: token-value epoch: epoch-value expires-at: until))))
+ (vector-set! state 6 next)
+ (hash-put! grants grant
+ (vector host verification-host request token epoch until
+ (poo-flow-authorized-effect-token-digest token) #f))
+ grant))))))
+
+;;; Reservation consumes the grant once, but executes no external effect.
+(def (assurance-effect-host-reserve! host grant)
+ (let* ((state (entry host)) (issued (grant-entry host grant)))
+ (and (current? state issued) (not (vector-ref issued 7))
+ (let (reservation (.o kind: 'aitia.effect-reservation.v1))
+ (vector-set! issued 7 #t)
+ (hash-put! reservations reservation (vector host grant issued #f))
+ reservation))))
+
+(def (delivery-receipt outcome-value invoked?)
+ (.o kind: 'aitia.effect-delivery.v1 outcome: outcome-value handoff-executed?: invoked?
+ delivered?: (eq? outcome-value 'delivered) durable?: #f distributed-authority?: #f))
+(def (assurance-effect-host-deliver! host reservation)
+ (let* ((state (entry host)) (issued (hash-get reservations reservation)))
+ (cond
+ ((or (not issued) (not (eq? host (vector-ref issued 0))) (vector-ref issued 3))
+ (delivery-receipt 'rejected #f))
+ (else
+ ;; Consume even on rejection / exception / unknown acknowledgement.
+ (vector-set! issued 3 #t)
+ (let (grant-state (vector-ref issued 2))
+ (if (and (grant-entry host (vector-ref issued 1)) (current? state grant-state))
+ (let (outcome
+ (handoff state
+ (lambda () ((vector-ref state 2) (vector-ref grant-state 2)
+ (vector-ref grant-state 3)))))
+ (unless (memq outcome '(delivered failed indeterminate))
+ (error "runtime delivery returned an invalid acknowledgement" outcome))
+ (delivery-receipt outcome #t))
+ (delivery-receipt 'rejected #f)))))))
diff --git a/modules/assurance/evidence-admission.ss b/modules/assurance/evidence-admission.ss
new file mode 100644
index 0000000..4c47446
--- /dev/null
+++ b/modules/assurance/evidence-admission.ss
@@ -0,0 +1,291 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; Pure eligibility and sealed support checks. The Host still owns adapter
+;;; identity, execution attestation and the transition to admitted evidence.
+(import (only-in :clan/poo/object .o .ref)
+ (only-in :gerbil/core list-sort)
+ (only-in :std/list/list find every)
+ :poo-flow/core/poo-clos/interface
+ (only-in :poo-flow/core/contribution/verification
+ poo-flow-verification-valid?)
+ (only-in :poo-flow/lambda-aitia/modules/assurance/funs
+ assurance-canonical-digest assurance-node-canonical
+ assurance-relation-canonical)
+ :poo-flow/lambda-aitia/modules/assurance/types)
+
+(export assurance-verifier-input assurance-verifier-outcome
+ assurance-evaluate-evidence-admission
+ assurance-evidence-outcome-bound?
+ assurance-verification-subject
+ assurance-snapshot-semantic-digest
+ assurance-verification-subject-snapshot
+ assurance-candidate-support-structural?
+ assurance-support-sealed-under-adapter?)
+
+(def (assurance-verifier-input artifact)
+ (unless (assurance-artifact? artifact)
+ (error "verifier input requires an artifact"))
+ (poo-clos-check-model
+ AssuranceVerifierInput
+ (.o (:: @ (poo-clos-model-prototype AssuranceVerifierInput))
+ identity: (.ref artifact 'identity)
+ revision: (.ref artifact 'revision)
+ digest: (.ref artifact 'content-digest))))
+
+(def (assurance-verifier-outcome identity-value status-value
+ producer: producer-value tool: tool-value
+ tool-version: tool-version-value
+ obligation: obligation-value
+ subject: subject-value scope: scope-value
+ snapshot-revision: snapshot-revision-value
+ snapshot-context-digest: context-value
+ inputs: input-values
+ output-digest: (output-value #f)
+ accountable-authority: (authority-value #f)
+ review-scope: (review-scope-value #f))
+ (poo-clos-check-model
+ AssuranceVerifierOutcome
+ (.o (:: @ (poo-clos-model-prototype AssuranceVerifierOutcome))
+ identity: identity-value status: status-value
+ producer: producer-value tool: tool-value
+ tool-version: tool-version-value
+ obligation: obligation-value subject: subject-value scope: scope-value
+ snapshot-revision: snapshot-revision-value
+ snapshot-context-digest: context-value inputs: input-values
+ output-digest: output-value accountable-authority: authority-value
+ review-scope: review-scope-value)))
+
+(def (snapshot-node snapshot identity)
+ (find (lambda (node) (string=? (.ref node 'identity) identity))
+ (.ref snapshot 'nodes)))
+(def (current-input? snapshot input)
+ (let ((artifact (snapshot-node snapshot (.ref input 'identity))))
+ (and artifact (assurance-artifact? artifact)
+ (string=? (.ref input 'revision) (.ref artifact 'revision))
+ (string=? (.ref input 'digest) (.ref artifact 'content-digest)))))
+(def (duplicate-inputs? inputs)
+ (let loop ((rest inputs) (seen '()))
+ (and (pair? rest)
+ (let ((identity (.ref (car rest) 'identity)))
+ (or (member identity seen)
+ (loop (cdr rest) (cons identity seen)))))))
+
+(def (outcome-canonical reported-result)
+ (list (.ref reported-result 'identity) (.ref reported-result 'status)
+ (.ref reported-result 'producer) (.ref reported-result 'tool)
+ (.ref reported-result 'tool-version)
+ (.ref reported-result 'obligation) (.ref reported-result 'subject)
+ (.ref reported-result 'scope)
+ (.ref reported-result 'snapshot-revision)
+ (.ref reported-result 'snapshot-context-digest)
+ (map (lambda (input)
+ (list (.ref input 'identity) (.ref input 'revision)
+ (.ref input 'digest)))
+ (.ref reported-result 'inputs))
+ (.ref reported-result 'output-digest)
+ (.ref reported-result 'accountable-authority)
+ (.ref reported-result 'review-scope)))
+
+(def (assurance-verification-subject snapshot-value obligation-value
+ evidence-value outcome-value)
+ (poo-clos-check-model
+ AssuranceVerificationSubject
+ (.o (:: @ (poo-clos-model-prototype AssuranceVerificationSubject))
+ snapshot: snapshot-value obligation: obligation-value
+ evidence: evidence-value outcome: outcome-value)))
+
+(def (snapshot-semantic-fields snapshot-value)
+ (list (.ref snapshot-value 'identity)
+ (.ref snapshot-value 'revision)
+ (.ref snapshot-value 'graph-identity)
+ (.ref snapshot-value 'source-revisions)
+ (.ref snapshot-value 'claim-revisions)
+ (.ref snapshot-value 'fact-cut)
+ (.ref snapshot-value 'policy-identity)
+ (.ref snapshot-value 'policy-revision)
+ (.ref snapshot-value 'evidence-identities)
+ (.ref snapshot-value 'state)
+ (.ref snapshot-value 'context-digest)
+ (.ref snapshot-value 'digest)
+ (map assurance-node-canonical (.ref snapshot-value 'nodes))
+ (map assurance-relation-canonical
+ (.ref snapshot-value 'relations))
+ (.ref snapshot-value 'unresolved)
+ (.ref snapshot-value 'conflicts)))
+
+;;; Use the entire semantic snapshot, not its editable displayed digest slot,
+;;; when the Host detects a change and advances its invalidation epoch.
+(def (assurance-snapshot-semantic-digest snapshot-value)
+ (unless (assurance-snapshot? snapshot-value)
+ (error "invalid assurance snapshot"))
+ (assurance-canonical-digest
+ (cons 'lambda-aitia.snapshot-semantic
+ (snapshot-semantic-fields snapshot-value))))
+
+;;; POO Flow's adapter freezes this string before and after its host-trusted
+;;; operation. Recompute from every semantic field: mutating only a displayed
+;;; snapshot digest must not preserve an issued seal.
+(def (assurance-verification-subject-snapshot subject-value)
+ (unless (assurance-verification-subject? subject-value)
+ (error "invalid assurance verification subject"))
+ (assurance-canonical-digest
+ (append
+ (cons 'lambda-aitia.verification-subject
+ (snapshot-semantic-fields (.ref subject-value 'snapshot)))
+ (list (assurance-node-canonical (.ref subject-value 'obligation))
+ (assurance-node-canonical (.ref subject-value 'evidence))
+ (outcome-canonical (.ref subject-value 'outcome))))))
+
+(def (snapshot-relation snapshot-value identity-value)
+ (find (lambda (relation)
+ (string=? (.ref relation 'identity) identity-value))
+ (.ref snapshot-value 'relations)))
+(def (same-text-inventory? left right)
+ (and (= (length left) (length right))
+ (equal? (list-sort string left) (list-sort string right))))
+
+(def (assurance-evidence-outcome-bound? snapshot evidence outcome)
+ (and (assurance-snapshot? snapshot)
+ (assurance-evidence? evidence)
+ (assurance-verifier-outcome? outcome)
+ (let (current-evidence
+ (snapshot-node snapshot (.ref evidence 'identity)))
+ (and current-evidence (assurance-evidence? current-evidence)
+ (equal? (assurance-node-canonical current-evidence)
+ (assurance-node-canonical evidence))
+ (equal? (.ref evidence 'content-digest)
+ (.ref outcome 'output-digest))
+ (equal? (.ref evidence 'producer) (.ref outcome 'producer))
+ (equal? (.ref evidence 'tool) (.ref outcome 'tool))
+ (equal? (.ref evidence 'tool-version)
+ (.ref outcome 'tool-version))
+ (same-text-inventory?
+ (.ref evidence 'input-artifacts)
+ (map (lambda (input) (.ref input 'identity))
+ (.ref outcome 'inputs)))))))
+
+;;; Shape alone never grants support. This check is private to the sealed
+;;; admission boundary and cannot be used as a public authorization shortcut.
+(def (structural-support? relation evidence obligation admission-state)
+ (and (assurance-relation? relation)
+ (assurance-evidence? evidence)
+ (assurance-obligation? obligation)
+ (eq? (.ref relation 'plane) 'assurance)
+ (memq (.ref relation 'relation) '(supports discharges))
+ (equal? (.ref relation 'source) (.ref evidence 'identity))
+ (equal? (.ref relation 'target) (.ref obligation 'identity))
+ (equal? (.ref evidence 'obligation) (.ref obligation 'identity))
+ (equal? (.ref evidence 'subject) (.ref obligation 'subject))
+ (equal? (.ref evidence 'scope) (.ref obligation 'scope))
+ (equal? (.ref evidence 'revision) (.ref obligation 'revision))
+ (eq? (.ref evidence 'state) 'supported)
+ (eq? (.ref evidence 'admission-state) admission-state)
+ (not (memq (.ref relation 'modality) '(hypothesized counterfactual)))))
+
+;;; A relation is only a structural witness until a Host checks the separate
+;;; admission capability. The candidate state is never rewritten in the cut.
+(def (assurance-candidate-support-structural?
+ relation evidence obligation snapshot)
+ (and (structural-support? relation evidence obligation 'candidate)
+ (assurance-snapshot? snapshot)
+ (let (current
+ (snapshot-relation snapshot (.ref relation 'identity)))
+ (and current
+ (equal? (assurance-relation-canonical current)
+ (assurance-relation-canonical relation))))))
+
+;;; Conditional low-level check: this does not establish that the supplied
+;;; adapter and clock belong to the application-configured Host.
+(def (assurance-support-sealed-under-adapter?
+ relation evidence obligation snapshot outcome adapter issued-receipt now)
+ (and (structural-support? relation evidence obligation 'admitted)
+ (assurance-snapshot? snapshot)
+ (assurance-verifier-outcome? outcome)
+ (let ((current-relation
+ (snapshot-relation snapshot (.ref relation 'identity)))
+ (structural
+ (assurance-evaluate-evidence-admission
+ snapshot obligation outcome)))
+ (and (assurance-evidence-outcome-bound? snapshot evidence outcome)
+ current-relation
+ (equal? (assurance-relation-canonical current-relation)
+ (assurance-relation-canonical relation))
+ (.ref structural 'eligible?)
+ (poo-flow-verification-valid?
+ adapter issued-receipt
+ (assurance-verification-subject
+ snapshot obligation evidence outcome)
+ now)))))
+
+(def (assurance-evaluate-evidence-admission snapshot obligation outcome)
+ (unless (and (assurance-snapshot? snapshot)
+ (assurance-obligation? obligation)
+ (assurance-verifier-outcome? outcome))
+ (error "invalid evidence admission inputs"))
+ (let* ((reported-result outcome)
+ (required-obligation obligation)
+ (current-snapshot snapshot)
+ (inputs (.ref outcome 'inputs))
+ (current-obligation
+ (snapshot-node snapshot (.ref obligation 'identity)))
+ (admission-blocker
+ (cond
+ ((not (eq? (.ref outcome 'status) 'succeeded))
+ 'outcome-not-successful)
+ ((pair? (.ref snapshot 'conflicts)) 'snapshot-conflicted)
+ ((or (not current-obligation)
+ (not (assurance-obligation? current-obligation))
+ (not (equal? (assurance-node-canonical obligation)
+ (assurance-node-canonical current-obligation)))
+ (not (equal? (.ref obligation 'snapshot)
+ (.ref snapshot 'identity)))
+ (not (equal? (.ref obligation 'snapshot-revision)
+ (.ref snapshot 'revision)))
+ (not (equal? (.ref obligation 'snapshot-context-digest)
+ (.ref snapshot 'context-digest))))
+ 'stale-obligation)
+ ((or (not (equal? (.ref outcome 'obligation)
+ (.ref obligation 'identity)))
+ (not (equal? (.ref outcome 'subject)
+ (.ref obligation 'subject)))
+ (not (equal? (.ref outcome 'scope)
+ (.ref obligation 'scope)))
+ (not (equal? (.ref outcome 'snapshot-revision)
+ (.ref snapshot 'revision)))
+ (not (equal? (.ref outcome 'snapshot-context-digest)
+ (.ref snapshot 'context-digest))))
+ 'outcome-binding-mismatch)
+ ((not (.ref outcome 'output-digest)) 'missing-output)
+ ((null? inputs) 'missing-inputs)
+ ((duplicate-inputs? inputs) 'duplicate-inputs)
+ ((not (every (lambda (input) (current-input? snapshot input))
+ inputs))
+ 'input-binding-mismatch)
+ ((and (eq? (.ref obligation 'evidence-kind) 'human-review)
+ (or (not (.ref outcome 'accountable-authority))
+ (not (.ref outcome 'review-scope))
+ (not (equal? (.ref outcome 'review-scope)
+ (.ref obligation 'scope)))))
+ 'human-authority-missing)
+ (else #f)))
+ (reported-digest
+ (assurance-canonical-digest (outcome-canonical reported-result)))
+ (receipt-digest
+ (assurance-canonical-digest
+ (list 'lambda-aitia.evidence-admission
+ reported-digest (assurance-node-canonical required-obligation)
+ (.ref current-snapshot 'digest) admission-blocker))))
+ (poo-clos-check-model
+ AssuranceEvidenceAdmissionReceipt
+ (.o (:: @ (poo-clos-model-prototype AssuranceEvidenceAdmissionReceipt))
+ schema: "lambda-aitia.evidence-admission"
+ outcome: (.ref reported-result 'identity)
+ obligation: (.ref required-obligation 'identity)
+ outcome-digest: reported-digest
+ snapshot-digest: (.ref current-snapshot 'digest)
+ digest: receipt-digest
+ eligible?: (not admission-blocker) blocker: admission-blocker
+ verifier-executed?: #f release-authorized?: #f))))
diff --git a/modules/assurance/funs.ss b/modules/assurance/funs.ss
index d770a02..d87e7d7 100644
--- a/modules/assurance/funs.ss
+++ b/modules/assurance/funs.ss
@@ -8,15 +8,27 @@
(only-in :gerbil/core list-sort string->utf8)
:std/list/list
(only-in :std/encoding/hex hex-encode)
- :poo-flow/src/module-system/contribution/model
+ (only-in :poo-flow/src/graph/algorithms
+ poo-flow-graph-cycle-path
+ poo-flow-graph-loop-analysis-receipt
+ poo-flow-graph-reachable-ids
+ poo-flow-graph-topological-order)
+ :poo-flow/core/poo-clos/interface
:poo-flow/lambda-aitia/modules/assurance/types
(only-in :poo-flow/lambda-aitia/modules/assurance/invalidation-projection
- assurance-invalidation-analysis assurance-invalidation-graph))
+ assurance-invalidation-analysis assurance-invalidation-graph)
+ (only-in :poo-flow/lambda-aitia/modules/assurance/verification-projection
+ assurance-verification-dependency-graph))
(export assurance-node-canonical assurance-relation-canonical
assurance-canonical-digest assurance-snapshot
- assurance-support-admissible? assurance-invalidation-graph
- assurance-invalidate)
+ assurance-snapshot-canonical?
+ assurance-bind-obligation-to-snapshot
+ assurance-invalidation-graph
+ assurance-invalidate assurance-derive-replacement-requirements
+ assurance-declare-replacement-obligation
+ assurance-plan-verification assurance-explain-verifier-choices
+ assurance-derive-composition-requirements)
(def (assurance-node-semantic-slots kind)
(case kind
@@ -28,7 +40,8 @@
((event) '(subject event-kind observation payload-digest modality
commitment-state causal-parents))
((action) '(subject action-kind requested-by scope))
- ((obligation) '(subject claim snapshot evidence-kind capability scope))
+ ((obligation) '(subject claim snapshot snapshot-revision
+ snapshot-context-digest evidence-kind capability scope))
((evidence) '(producer tool tool-version input-artifacts obligation subject
scope valid-from valid-until admission-state))
((counterexample) '(subject challenges evidence details-digest))
@@ -43,6 +56,16 @@
(.ref node 'state) (.ref node 'content-digest)
(map (lambda (name) (list name (.ref node name)))
(assurance-node-semantic-slots (.ref node 'kind)))))
+;;; The context projection excludes only the obligation's own binding slots.
+;;; Every other semantic change, including another obligation or relation,
+;;; changes the context digest without creating a self-referential hash.
+(def (assurance-node-context-canonical node)
+ (if (assurance-obligation? node)
+ (list (.ref node 'identity) (.ref node 'kind) (.ref node 'revision)
+ (.ref node 'state) (.ref node 'content-digest)
+ (map (lambda (name) (list name (.ref node name)))
+ '(subject claim snapshot evidence-kind capability scope)))
+ (assurance-node-canonical node)))
(def (assurance-relation-canonical relation)
(unless (assurance-relation? relation)
(error "invalid assurance relation" relation))
@@ -221,48 +244,85 @@
(memq 'hypothesized node-states)
(memq 'counterfactual node-states)) 'unknown)
(else 'supported)))
+ (context-canonical
+ (list 'lambda-aitia.assurance-snapshot-context
+ identity-value revision-value graph-identity-value
+ canonical-source-revisions canonical-claim-revisions
+ fact-cut-value policy-identity-value policy-revision-value
+ canonical-evidence-identities snapshot-state
+ (canonical-objects ordered-nodes
+ assurance-node-context-canonical)
+ (canonical-objects ordered-relations
+ assurance-relation-canonical)
+ missing all-conflicts))
+ (snapshot-context-digest
+ (assurance-canonical-digest context-canonical))
(canonical
(list 'lambda-aitia.assurance-snapshot identity-value revision-value
graph-identity-value canonical-source-revisions
canonical-claim-revisions
fact-cut-value policy-identity-value policy-revision-value
canonical-evidence-identities
- snapshot-state
+ snapshot-state snapshot-context-digest
(canonical-objects ordered-nodes assurance-node-canonical)
(canonical-objects ordered-relations assurance-relation-canonical)
missing all-conflicts))
(snapshot-digest (assurance-canonical-digest canonical)))
- (poo-flow-check-model
+ (poo-clos-check-model
AssuranceSnapshot
- (.o (:: @ (poo-flow-model-prototype AssuranceSnapshot))
+ (.o (:: @ (poo-clos-model-prototype AssuranceSnapshot))
identity: identity-value revision: revision-value
graph-identity: graph-identity-value
source-revisions: canonical-source-revisions
claim-revisions: canonical-claim-revisions fact-cut: fact-cut-value
policy-identity: policy-identity-value policy-revision: policy-revision-value
evidence-identities: canonical-evidence-identities
- state: snapshot-state digest: snapshot-digest
+ state: snapshot-state context-digest: snapshot-context-digest
+ digest: snapshot-digest
nodes: ordered-nodes relations: ordered-relations
unresolved: missing conflicts: all-conflicts)))))))))
-;;; Shape and a green-looking relation never grant support. The exact
-;;; evidence and obligation identities must match, and alternate modalities
-;;; cannot discharge an observed-fact obligation.
-(def (assurance-support-admissible? relation evidence obligation)
- (and (assurance-relation? relation)
- (assurance-evidence? evidence)
- (assurance-obligation? obligation)
- (eq? (.ref relation 'plane) 'assurance)
- (memq (.ref relation 'relation) '(supports discharges))
- (equal? (.ref relation 'source) (.ref evidence 'identity))
- (equal? (.ref relation 'target) (.ref obligation 'identity))
- (equal? (.ref evidence 'obligation) (.ref obligation 'identity))
- (equal? (.ref evidence 'subject) (.ref obligation 'subject))
- (equal? (.ref evidence 'scope) (.ref obligation 'scope))
- (equal? (.ref evidence 'revision) (.ref obligation 'revision))
- (eq? (.ref evidence 'state) 'supported)
- (eq? (.ref evidence 'admission-state) 'admitted)
- (not (memq (.ref relation 'modality) '(hypothesized counterfactual)))))
+;;; A Host source must supply a self-consistent snapshot, not an editable POO
+;;; presentation whose displayed digest/context no longer match its inventory.
+(def (assurance-snapshot-canonical? snapshot)
+ (and (assurance-snapshot? snapshot)
+ (let (rebuilt
+ (assurance-snapshot
+ (.ref snapshot 'identity) (.ref snapshot 'revision)
+ (.ref snapshot 'graph-identity)
+ (.ref snapshot 'source-revisions)
+ (.ref snapshot 'claim-revisions)
+ (.ref snapshot 'fact-cut)
+ (.ref snapshot 'policy-identity)
+ (.ref snapshot 'policy-revision)
+ (.ref snapshot 'nodes) (.ref snapshot 'relations)
+ evidence-identities: (.ref snapshot 'evidence-identities)
+ unresolved: (.ref snapshot 'unresolved)))
+ (and (equal? (.ref snapshot 'digest) (.ref rebuilt 'digest))
+ (equal? (.ref snapshot 'context-digest)
+ (.ref rebuilt 'context-digest))
+ (eq? (.ref snapshot 'state) (.ref rebuilt 'state))
+ (equal? (.ref snapshot 'conflicts) (.ref rebuilt 'conflicts))
+ (equal? (.ref snapshot 'unresolved)
+ (.ref rebuilt 'unresolved))))))
+
+;;; Binding is explicit: snapshot construction never silently upgrades an old
+;;; obligation. The caller must reconstruct the final snapshot with this value;
+;;; planning then compares both revision and non-circular context digest.
+(def (assurance-bind-obligation-to-snapshot obligation snapshot)
+ (unless (and (assurance-obligation? obligation)
+ (assurance-snapshot? snapshot)
+ (string=? (.ref obligation 'snapshot) (.ref snapshot 'identity))
+ (not (.ref obligation 'snapshot-revision))
+ (not (.ref obligation 'snapshot-context-digest)))
+ (error "obligation cannot bind to this snapshot"))
+ (let ((bound-revision (.ref snapshot 'revision))
+ (bound-context (.ref snapshot 'context-digest)))
+ (poo-clos-check-model
+ AssuranceObligation
+ (.o (:: @ obligation)
+ snapshot-revision: bound-revision
+ snapshot-context-digest: bound-context))))
(def (identity-member? identity values)
(if (member identity values) #t #f))
@@ -311,3 +371,669 @@
(impacted-kind-identities nodes impacted 'effect)
witnesses unresolved strong-components cyclic-components
#f #f #f)))))
+
+;;; This derives the need for fresh declarations from the old snapshot's
+;;; invalidation closure. It deliberately does not make a new obligation: a
+;;; revised claim or policy may require a different question or evidence kind,
+;;; and inserting an obligation would change the target snapshot context.
+(def (replacement-requirement-blocker source-obligation source target
+ change-unresolved)
+ (let ((source-claim
+ (node-by-identity (.ref source 'nodes)
+ (.ref source-obligation 'claim)))
+ (target-claim
+ (node-by-identity (.ref target 'nodes)
+ (.ref source-obligation 'claim))))
+ (cond
+ ((pair? (.ref source 'conflicts)) 'source-conflicted)
+ ((pair? (.ref source 'unresolved)) 'source-unresolved)
+ ((pair? change-unresolved) 'change-unresolved)
+ ((or (not (equal? (.ref source-obligation 'snapshot)
+ (.ref source 'identity)))
+ (not (equal? (.ref source-obligation 'snapshot-revision)
+ (.ref source 'revision)))
+ (not (equal? (.ref source-obligation 'snapshot-context-digest)
+ (.ref source 'context-digest))))
+ 'stale-source)
+ ((equal? (.ref source 'digest) (.ref target 'digest)) 'target-unchanged)
+ ((pair? (.ref target 'conflicts)) 'target-conflicted)
+ ((pair? (.ref target 'unresolved)) 'target-unresolved)
+ ((not (equal? (.ref source 'identity) (.ref target 'identity)))
+ 'target-identity-changed)
+ ((not (equal? (.ref source 'graph-identity)
+ (.ref target 'graph-identity)))
+ 'graph-changed)
+ ((or (not (equal? (.ref source 'policy-identity)
+ (.ref target 'policy-identity)))
+ (not (equal? (.ref source 'policy-revision)
+ (.ref target 'policy-revision))))
+ 'policy-changed)
+ ((not (assurance-claim? target-claim)) 'claim-missing)
+ ((or (not (assurance-claim? source-claim))
+ (not (equal? (assurance-node-canonical source-claim)
+ (assurance-node-canonical target-claim))))
+ 'claim-changed)
+ (else #f))))
+
+(def (assurance-derive-replacement-requirements derivation-identity source target
+ changed-identities)
+ (unless (and (assurance-text? derivation-identity)
+ (assurance-snapshot? source)
+ (assurance-snapshot? target))
+ (error "invalid replacement derivation input"))
+ (let* ((invalidation
+ (assurance-invalidate
+ (string-append derivation-identity "/invalidation")
+ source changed-identities))
+ (changed-values
+ (assurance-invalidation-receipt-ref invalidation 'changed))
+ (change-witnesses
+ (assurance-invalidation-receipt-ref invalidation 'witnesses))
+ (change-unresolved
+ (assurance-invalidation-receipt-ref invalidation 'unresolved))
+ (obligation-ids
+ (assurance-invalidation-receipt-ref
+ invalidation 'required-obligations))
+ (derived-requirements
+ (map
+ (lambda (obligation-id)
+ (let ((obligation
+ (node-by-identity (.ref source 'nodes) obligation-id)))
+ (poo-clos-check-model
+ AssuranceReplacementRequirement
+ (.o (:: @ (poo-clos-model-prototype
+ AssuranceReplacementRequirement))
+ source-obligation: obligation-id
+ source-revision: (.ref obligation 'revision)
+ claim: (.ref obligation 'claim)
+ subject: (.ref obligation 'subject)
+ evidence-kind: (.ref obligation 'evidence-kind)
+ capability: (.ref obligation 'capability)
+ scope: (.ref obligation 'scope)
+ blocker:
+ (replacement-requirement-blocker
+ obligation source target change-unresolved)))))
+ obligation-ids))
+ (derivation-digest
+ (assurance-canonical-digest
+ (list "lambda-aitia.replacement-requirements"
+ derivation-identity
+ (.ref source 'digest) (.ref target 'digest)
+ changed-values change-witnesses change-unresolved
+ (map (lambda (requirement)
+ (list (.ref requirement 'source-obligation)
+ (.ref requirement 'source-revision)
+ (.ref requirement 'claim)
+ (.ref requirement 'subject)
+ (.ref requirement 'evidence-kind)
+ (.ref requirement 'capability)
+ (.ref requirement 'scope)
+ (.ref requirement 'blocker)))
+ derived-requirements)))))
+ (poo-clos-check-model
+ AssuranceReplacementDerivation
+ (.o (:: @ (poo-clos-model-prototype AssuranceReplacementDerivation))
+ schema: "lambda-aitia.replacement-requirements"
+ identity: derivation-identity
+ source-snapshot-digest: (.ref source 'digest)
+ target-snapshot-digest: (.ref target 'digest)
+ changed: changed-values
+ witnesses: change-witnesses
+ unresolved: change-unresolved
+ requirements: derived-requirements digest: derivation-digest
+ verifier-executed?: #f release-authorized?: #f))))
+
+;;; The new obligation, revised claim and supports edge must already be present
+;;; in the caller's draft. Only its snapshot binding is filled here. The
+;;; derivation is replayed against exact source/draft digests so a forged or
+;;; unrelated receipt cannot be used to promote a candidate.
+(def (snapshot-canonical? snapshot)
+ (let ((rebuilt
+ (assurance-snapshot
+ (.ref snapshot 'identity) (.ref snapshot 'revision)
+ (.ref snapshot 'graph-identity)
+ (.ref snapshot 'source-revisions) (.ref snapshot 'claim-revisions)
+ (.ref snapshot 'fact-cut) (.ref snapshot 'policy-identity)
+ (.ref snapshot 'policy-revision)
+ (.ref snapshot 'nodes) (.ref snapshot 'relations)
+ evidence-identities: (.ref snapshot 'evidence-identities)
+ unresolved: (.ref snapshot 'unresolved))))
+ (and (equal? (.ref rebuilt 'digest) (.ref snapshot 'digest))
+ (equal? (.ref rebuilt 'context-digest)
+ (.ref snapshot 'context-digest)))))
+
+(def (replacement-declaration-blocker derivation source draft
+ source-id candidate-id)
+ (let* ((expected
+ (assurance-derive-replacement-requirements
+ (.ref derivation 'identity) source draft (.ref derivation 'changed)))
+ (requirement
+ (find (lambda (item)
+ (equal? (.ref item 'source-obligation) source-id))
+ (.ref derivation 'requirements)))
+ (candidate (node-by-identity (.ref draft 'nodes) candidate-id))
+ (claim (and (assurance-obligation? candidate)
+ (node-by-identity (.ref draft 'nodes)
+ (.ref candidate 'claim))))
+ (support-links
+ (filter (lambda (relation)
+ (and (eq? (.ref relation 'relation) 'supports)
+ (equal? (.ref relation 'source) candidate-id)
+ (assurance-obligation? candidate)
+ (equal? (.ref relation 'target)
+ (.ref candidate 'claim))))
+ (.ref draft 'relations))))
+ (cond
+ ((or (not (snapshot-canonical? source))
+ (not (snapshot-canonical? draft))
+ (not (equal? (.ref derivation 'source-snapshot-digest)
+ (.ref source 'digest)))
+ (not (equal? (.ref derivation 'target-snapshot-digest)
+ (.ref draft 'digest)))
+ (not (equal? (.ref derivation 'digest) (.ref expected 'digest))))
+ 'derivation-mismatch)
+ ((not requirement) 'source-requirement-missing)
+ ((not (memq (.ref requirement 'blocker) '(#f claim-changed)))
+ 'source-requirement-blocked)
+ ((or (pair? (.ref draft 'unresolved)) (pair? (.ref draft 'conflicts)))
+ 'target-frontier)
+ ((not (assurance-obligation? candidate)) 'candidate-missing)
+ ((node-by-identity (.ref source 'nodes) candidate-id)
+ 'candidate-not-fresh)
+ ((or (not (equal? (.ref candidate 'snapshot) (.ref draft 'identity)))
+ (.ref candidate 'snapshot-revision)
+ (.ref candidate 'snapshot-context-digest))
+ 'candidate-not-unbound)
+ ((not (eq? (.ref candidate 'state) 'unknown)) 'candidate-not-unknown)
+ ((not (equal? (.ref candidate 'claim) (.ref requirement 'claim)))
+ 'candidate-claim-mismatch)
+ ((not (assurance-claim? claim)) 'candidate-claim-mismatch)
+ ((not (equal? (.ref candidate 'subject) (.ref claim 'subject)))
+ 'candidate-subject-mismatch)
+ ((not (equal? (.ref candidate 'scope) (.ref claim 'scope)))
+ 'candidate-scope-mismatch)
+ ((not (member candidate-id (.ref claim 'support-requirements)))
+ 'claim-not-declared)
+ ((null? support-links) 'support-link-missing)
+ ((not (find (lambda (relation)
+ (memq (.ref relation 'modality)
+ '(observed declared derived)))
+ support-links))
+ 'support-link-hypothetical)
+ (else #f))))
+
+(def (assurance-declare-replacement-obligation declaration-identity source draft derivation
+ source-id candidate-id)
+ (unless (and (assurance-text? declaration-identity)
+ (assurance-text? source-id) (assurance-text? candidate-id)
+ (assurance-snapshot? source) (assurance-snapshot? draft)
+ (assurance-replacement-derivation? derivation))
+ (error "invalid replacement declaration input"))
+ (let* ((declaration-blocker
+ (replacement-declaration-blocker
+ derivation source draft source-id candidate-id))
+ (final-snapshot
+ (and (not declaration-blocker)
+ (let* ((candidate
+ (node-by-identity (.ref draft 'nodes) candidate-id))
+ (bound
+ (assurance-bind-obligation-to-snapshot candidate draft)))
+ (assurance-snapshot
+ (.ref draft 'identity) (.ref draft 'revision)
+ (.ref draft 'graph-identity)
+ (.ref draft 'source-revisions) (.ref draft 'claim-revisions)
+ (.ref draft 'fact-cut) (.ref draft 'policy-identity)
+ (.ref draft 'policy-revision)
+ (map (lambda (node)
+ (if (equal? (.ref node 'identity) candidate-id)
+ bound node))
+ (.ref draft 'nodes))
+ (.ref draft 'relations)
+ evidence-identities: (.ref draft 'evidence-identities)
+ unresolved: (.ref draft 'unresolved)))))
+ (final-digest (and final-snapshot (.ref final-snapshot 'digest)))
+ (receipt-digest
+ (assurance-canonical-digest
+ (list "lambda-aitia.replacement-declaration" declaration-identity
+ (.ref derivation 'digest) (.ref source 'digest)
+ (.ref draft 'digest) final-digest
+ source-id candidate-id declaration-blocker)))
+ (receipt
+ (poo-clos-check-model
+ AssuranceReplacementDeclarationReceipt
+ (.o (:: @ (poo-clos-model-prototype
+ AssuranceReplacementDeclarationReceipt))
+ schema: "lambda-aitia.replacement-declaration"
+ identity: declaration-identity
+ derivation-digest: (.ref derivation 'digest)
+ source-snapshot-digest: (.ref source 'digest)
+ draft-snapshot-digest: (.ref draft 'digest)
+ final-snapshot-digest: final-digest
+ source-obligation: source-id target-obligation: candidate-id
+ blocker: declaration-blocker digest: receipt-digest
+ verifier-executed?: #f release-authorized?: #f))))
+ (when (and final-snapshot
+ (not (equal? (.ref final-snapshot 'context-digest)
+ (.ref draft 'context-digest))))
+ (error "replacement binding changed snapshot context"))
+ (values final-snapshot receipt)))
+
+;;; A structural 'composes edge declares a composite claim's components.
+;;; Only an independent, explicitly linked and current obligation can answer
+;;; its composition question; this receipt never discharges that obligation.
+(def (composition-component-ids snapshot composition-id)
+ (ordered-unique-text
+ (map (lambda (relation) (.ref relation 'target))
+ (filter (lambda (relation)
+ (and (eq? (.ref relation 'relation) 'composes)
+ (equal? (.ref relation 'source) composition-id)))
+ (.ref snapshot 'relations)))))
+
+(def (composition-direct-obligations snapshot composition-id claim-node)
+ (if (not (assurance-claim? claim-node))
+ '()
+ (ordered-unique-text
+ (map
+ (lambda (obligation) (.ref obligation 'identity))
+ (filter
+ (lambda (obligation)
+ (and (assurance-obligation? obligation)
+ (equal? (.ref obligation 'claim) composition-id)
+ (equal? (.ref obligation 'subject)
+ (.ref claim-node 'subject))
+ (equal? (.ref obligation 'scope)
+ (.ref claim-node 'scope))
+ (member (.ref obligation 'identity)
+ (.ref claim-node 'support-requirements))
+ (find
+ (lambda (relation)
+ (and (eq? (.ref relation 'relation) 'supports)
+ (eq? (.ref relation 'plane) 'assurance)
+ (memq (.ref relation 'modality)
+ '(declared observed derived))
+ (equal? (.ref relation 'source)
+ (.ref obligation 'identity))
+ (equal? (.ref relation 'target) composition-id)))
+ (.ref snapshot 'relations))))
+ (.ref snapshot 'nodes))))))
+
+(def (composition-current-obligation? snapshot obligation-id)
+ (let ((obligation (node-by-identity (.ref snapshot 'nodes) obligation-id)))
+ (and (assurance-obligation? obligation)
+ (equal? (.ref obligation 'snapshot) (.ref snapshot 'identity))
+ (equal? (.ref obligation 'snapshot-revision)
+ (.ref snapshot 'revision))
+ (equal? (.ref obligation 'snapshot-context-digest)
+ (.ref snapshot 'context-digest)))))
+
+(def (composition-requirement-blocker snapshot composition-claim
+ component-ids direct-ids current-ids)
+ (cond
+ ((pair? (.ref snapshot 'conflicts)) 'conflicted-snapshot)
+ ((pair? (.ref snapshot 'unresolved)) 'unresolved-frontier)
+ ((not (assurance-claim? composition-claim))
+ 'invalid-composition-claim)
+ ((< (length component-ids) 2) 'insufficient-components)
+ ((find (lambda (component-id)
+ (not (assurance-claim?
+ (node-by-identity (.ref snapshot 'nodes) component-id))))
+ component-ids)
+ 'invalid-component-claim)
+ ((pair? current-ids) #f)
+ ((pair? direct-ids) 'stale-direct-obligation)
+ (else 'missing-direct-obligation)))
+
+(def (assurance-derive-composition-requirements derivation-identity snapshot)
+ (unless (and (assurance-text? derivation-identity)
+ (assurance-snapshot? snapshot))
+ (error "invalid composition derivation input"))
+ (let* ((composition-ids
+ (ordered-unique-text
+ (map (lambda (relation) (.ref relation 'source))
+ (filter
+ (lambda (relation)
+ (eq? (.ref relation 'relation) 'composes))
+ (.ref snapshot 'relations)))))
+ (derived-requirements
+ (map
+ (lambda (composition-id)
+ (let* ((composition-claim
+ (node-by-identity (.ref snapshot 'nodes)
+ composition-id))
+ (component-ids
+ (composition-component-ids snapshot composition-id))
+ (direct-ids
+ (composition-direct-obligations
+ snapshot composition-id composition-claim))
+ (current-ids
+ (filter
+ (lambda (obligation-id)
+ (composition-current-obligation?
+ snapshot obligation-id))
+ direct-ids))
+ (blocker-value
+ (composition-requirement-blocker
+ snapshot composition-claim component-ids
+ direct-ids current-ids)))
+ (poo-clos-check-model
+ AssuranceCompositionRequirement
+ (.o (:: @ (poo-clos-model-prototype
+ AssuranceCompositionRequirement))
+ composition-claim: composition-id
+ component-claims: component-ids
+ direct-obligations: direct-ids
+ current-obligations: current-ids
+ blocker: blocker-value))))
+ composition-ids))
+ (derivation-digest
+ (assurance-canonical-digest
+ (list "lambda-aitia.composition-requirements"
+ derivation-identity (.ref snapshot 'digest)
+ (map (lambda (requirement)
+ (list (.ref requirement 'composition-claim)
+ (.ref requirement 'component-claims)
+ (.ref requirement 'direct-obligations)
+ (.ref requirement 'current-obligations)
+ (.ref requirement 'blocker)))
+ derived-requirements)))))
+ (poo-clos-check-model
+ AssuranceCompositionDerivation
+ (.o (:: @ (poo-clos-model-prototype AssuranceCompositionDerivation))
+ schema: "lambda-aitia.composition-requirements"
+ identity: derivation-identity
+ snapshot-digest: (.ref snapshot 'digest)
+ requirements: derived-requirements
+ digest: derivation-digest
+ verifier-executed?: #f release-authorized?: #f))))
+
+;;; Phase 3 selection boundary. This plan names only obligations already bound
+;;; to the supplied snapshot. Deriving replacement revisions and invoking
+;;; verifiers belong to later phases; neither can be inferred from a change.
+(def (verification-base-blocker obligation snapshot capabilities
+ unresolved conflicts cyclic-ids
+ prerequisites planned-ids)
+ (cond
+ ((pair? conflicts) 'conflicted-snapshot)
+ ((pair? unresolved) 'unresolved-frontier)
+ ((member (.ref obligation 'identity) cyclic-ids) 'dependency-cycle)
+ ((or (not (string=? (.ref obligation 'snapshot)
+ (.ref snapshot 'identity)))
+ (not (equal? (.ref obligation 'snapshot-revision)
+ (.ref snapshot 'revision)))
+ (not (equal? (.ref obligation 'snapshot-context-digest)
+ (.ref snapshot 'context-digest))))
+ 'stale-obligation)
+ ((not (memq (.ref obligation 'capability) capabilities))
+ 'capability-unavailable)
+ ((find (lambda (id) (not (member id planned-ids))) prerequisites)
+ 'dependency-unplanned)
+ (else #f)))
+
+(def (assurance-plan-verification identity snapshot changed-identities policy)
+ (unless (and (assurance-text? identity)
+ (assurance-snapshot? snapshot)
+ (assurance-verification-policy? policy))
+ (error "invalid assurance verification planning input"))
+ (unless (and (string=? (.ref policy 'identity)
+ (.ref snapshot 'policy-identity))
+ (string=? (.ref policy 'revision)
+ (.ref snapshot 'policy-revision)))
+ (error "verification policy does not match the snapshot"))
+ (let* ((invalidation
+ (assurance-invalidate
+ (string-append identity "/invalidation")
+ snapshot changed-identities))
+ (obligation-ids
+ (assurance-invalidation-receipt-ref
+ invalidation 'required-obligations))
+ (changed
+ (assurance-invalidation-receipt-ref invalidation 'changed))
+ (impacted
+ (assurance-invalidation-receipt-ref invalidation 'impacted))
+ (blocked-effects
+ (assurance-invalidation-receipt-ref invalidation 'blocked-effects))
+ (witnesses
+ (assurance-invalidation-receipt-ref invalidation 'witnesses))
+ (unresolved
+ (assurance-invalidation-receipt-ref invalidation 'unresolved))
+ (conflicts (.ref snapshot 'conflicts))
+ (capabilities (.ref policy 'capabilities)))
+ (let-values (((dependency-graph prerequisites)
+ (assurance-verification-dependency-graph
+ snapshot obligation-ids)))
+ (let* ((cycle-path
+ (or (poo-flow-graph-cycle-path dependency-graph) '()))
+ (cyclic-components
+ (.ref (poo-flow-graph-loop-analysis-receipt dependency-graph)
+ 'cyclic-components))
+ (cyclic-ids (concatenate cyclic-components))
+ (cycle-blocked
+ (poo-flow-graph-reachable-ids dependency-graph cyclic-ids))
+ (base-blockers
+ (map
+ (lambda (obligation-id)
+ (cons
+ obligation-id
+ (verification-base-blocker
+ (node-by-identity (.ref snapshot 'nodes) obligation-id)
+ snapshot capabilities unresolved conflicts cyclic-ids
+ (cdr (assoc obligation-id prerequisites)) obligation-ids)))
+ obligation-ids))
+ (blocked-closure
+ (poo-flow-graph-reachable-ids
+ dependency-graph
+ (append cycle-blocked
+ (map car (filter (lambda (entry) (cdr entry))
+ base-blockers)))))
+ (available-ids
+ (filter (lambda (id) (not (member id blocked-closure)))
+ obligation-ids))
+ (available-order
+ (let-values (((available-graph _)
+ (assurance-verification-dependency-graph
+ snapshot available-ids)))
+ (poo-flow-graph-topological-order available-graph)))
+ (blocked-order
+ (filter (lambda (id) (member id blocked-closure))
+ (if (null? cycle-path)
+ (poo-flow-graph-topological-order dependency-graph)
+ obligation-ids)))
+ (ordered-ids (append available-order blocked-order))
+ (requests
+ (map
+ (lambda (obligation-id)
+ (let* ((obligation
+ (node-by-identity (.ref snapshot 'nodes)
+ obligation-id))
+ (base-reason (cdr (assoc obligation-id base-blockers)))
+ (request-reason
+ (or base-reason
+ (and (member obligation-id blocked-closure)
+ 'dependency-blocked)))
+ (request-id obligation-id)
+ (request-dependencies
+ (cdr (assoc obligation-id prerequisites))))
+ (poo-clos-check-model
+ AssuranceVerificationRequest
+ (.o (:: @ (poo-clos-model-prototype
+ AssuranceVerificationRequest))
+ obligation-identity: request-id
+ subject: (.ref obligation 'subject)
+ claim: (.ref obligation 'claim)
+ snapshot-digest: (.ref snapshot 'digest)
+ evidence-kind: (.ref obligation 'evidence-kind)
+ capability: (.ref obligation 'capability)
+ dependencies: request-dependencies
+ selected?: (not request-reason)
+ blocker: request-reason))))
+ ordered-ids))
+ (blocked
+ (map (lambda (request) (.ref request 'obligation-identity))
+ (filter (lambda (request) (not (.ref request 'selected?)))
+ requests)))
+ (selected
+ (map (lambda (request) (.ref request 'obligation-identity))
+ (filter (lambda (request) (.ref request 'selected?))
+ requests)))
+ (request-projection
+ (map (lambda (request)
+ (list (.ref request 'obligation-identity)
+ (.ref request 'subject) (.ref request 'claim)
+ (.ref request 'evidence-kind)
+ (.ref request 'capability)
+ (.ref request 'dependencies)
+ (.ref request 'selected?) (.ref request 'blocker)))
+ requests))
+ (digest
+ (assurance-canonical-digest
+ (list "lambda-aitia.planning-result" identity
+ (.ref snapshot 'digest)
+ (.ref policy 'identity) (.ref policy 'revision)
+ (.ref policy 'capabilities)
+ changed impacted blocked-effects witnesses
+ cycle-path cyclic-components
+ selected blocked request-projection
+ unresolved conflicts))))
+ (let ((plan-identity identity)
+ (plan-digest digest)
+ (plan-selected selected)
+ (plan-blocked blocked)
+ (plan-changed changed)
+ (plan-impacted impacted)
+ (plan-blocked-effects blocked-effects)
+ (plan-cycle-path cycle-path)
+ (plan-cyclic-components cyclic-components)
+ (plan-witnesses witnesses)
+ (plan-requests requests)
+ (plan-unresolved unresolved)
+ (plan-conflicts conflicts))
+ (poo-clos-check-model
+ AssuranceVerificationPlan
+ (.o (:: @ (poo-clos-model-prototype AssuranceVerificationPlan))
+ schema: "lambda-aitia.planning-result"
+ identity: plan-identity snapshot-digest: (.ref snapshot 'digest)
+ policy-identity: (.ref policy 'identity)
+ policy-revision: (.ref policy 'revision)
+ digest: plan-digest
+ selected-obligations: plan-selected
+ blocked-obligations: plan-blocked
+ changed: plan-changed impacted: plan-impacted
+ blocked-effects: plan-blocked-effects
+ cycle-path: plan-cycle-path
+ cyclic-components: plan-cyclic-components
+ witnesses: plan-witnesses
+ requests: plan-requests
+ unresolved: plan-unresolved conflicts: plan-conflicts
+ verifier-executed?: #f release-authorized?: #f
+ runtime-executed?: #f)))))))
+
+;;; Candidate selection is a pure explanation over explicit declarations.
+;;; It does not establish that an adapter is installed, runnable or trusted.
+(def (verifier-candidate-matches? candidate request)
+ (and (memq (.ref request 'evidence-kind)
+ (.ref candidate 'evidence-kinds))
+ (eq? (.ref request 'capability) (.ref candidate 'capability))))
+
+(def (verifier-choice-reason candidate request selected-candidate)
+ (cond
+ ((not (.ref request 'selected?)) 'request-blocked)
+ ((not (memq (.ref request 'evidence-kind)
+ (.ref candidate 'evidence-kinds)))
+ 'evidence-kind-unsupported)
+ ((not (eq? (.ref request 'capability)
+ (.ref candidate 'capability)))
+ 'capability-mismatch)
+ ((and selected-candidate
+ (equal? (.ref candidate 'identity)
+ (.ref selected-candidate 'identity)))
+ 'chosen)
+ (else 'lower-priority)))
+
+(def (assurance-explain-verifier-choices explanation-identity plan candidates)
+ (unless (and (assurance-text? explanation-identity)
+ (assurance-verification-plan? plan)
+ (list? candidates)
+ (every assurance-verifier-candidate? candidates))
+ (error "invalid verifier choice input"))
+ (let* ((ordered-candidates
+ (list-sort
+ (lambda (left right)
+ (or (< (.ref left 'priority) (.ref right 'priority))
+ (and (= (.ref left 'priority) (.ref right 'priority))
+ (string (.ref left 'identity)
+ (.ref right 'identity)))))
+ candidates))
+ (candidate-ids (map (lambda (candidate)
+ (.ref candidate 'identity))
+ ordered-candidates)))
+ (unless (= (length candidate-ids)
+ (length (delete-duplicates/hash candidate-ids)))
+ (error "duplicate verifier candidate identity" candidate-ids))
+ (let* ((requests (.ref plan 'requests))
+ (candidate-choices
+ (concatenate
+ (map
+ (lambda (request)
+ (let ((selected-candidate
+ (and (.ref request 'selected?)
+ (find (lambda (candidate)
+ (verifier-candidate-matches?
+ candidate request))
+ ordered-candidates))))
+ (map
+ (lambda (candidate)
+ (let ((choice-reason
+ (verifier-choice-reason
+ candidate request selected-candidate)))
+ (poo-clos-check-model
+ AssuranceVerifierChoice
+ (.o (:: @ (poo-clos-model-prototype
+ AssuranceVerifierChoice))
+ obligation-identity:
+ (.ref request 'obligation-identity)
+ candidate-identity: (.ref candidate 'identity)
+ selected?: (eq? choice-reason 'chosen)
+ reason: choice-reason))))
+ ordered-candidates)))
+ requests)))
+ (unassigned-ids
+ (map
+ (lambda (request) (.ref request 'obligation-identity))
+ (filter
+ (lambda (request)
+ (and (.ref request 'selected?)
+ (not (find
+ (lambda (candidate)
+ (verifier-candidate-matches?
+ candidate request))
+ ordered-candidates))))
+ requests)))
+ (explanation-digest
+ (assurance-canonical-digest
+ (list "lambda-aitia.verifier-choices"
+ explanation-identity (.ref plan 'digest)
+ (map (lambda (candidate)
+ (list (.ref candidate 'identity)
+ (.ref candidate 'revision)
+ (.ref candidate 'priority)
+ (.ref candidate 'evidence-kinds)
+ (.ref candidate 'capability)))
+ ordered-candidates)
+ (map (lambda (choice)
+ (list (.ref choice 'obligation-identity)
+ (.ref choice 'candidate-identity)
+ (.ref choice 'selected?)
+ (.ref choice 'reason)))
+ candidate-choices)
+ unassigned-ids))))
+ (poo-clos-check-model
+ AssuranceVerifierChoiceReceipt
+ (.o (:: @ (poo-clos-model-prototype AssuranceVerifierChoiceReceipt))
+ schema: "lambda-aitia.verifier-choices"
+ identity: explanation-identity
+ plan-digest: (.ref plan 'digest)
+ candidates: ordered-candidates
+ choices: candidate-choices
+ unassigned-obligations: unassigned-ids
+ digest: explanation-digest
+ verifier-executed?: #f release-authorized?: #f)))))
diff --git a/modules/assurance/host.ss b/modules/assurance/host.ss
new file mode 100644
index 0000000..0921611
--- /dev/null
+++ b/modules/assurance/host.ss
@@ -0,0 +1,728 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; An application-configured capability for verification. Adapter, clock and
+;;; lease are private to the exact issued Host object; neither an editable POO
+;;; presentation nor a per-call time argument selects authority.
+(import (only-in :clan/poo/object .o .ref .slot? .alist object?)
+ (only-in :std/list/list find every filter delete-duplicates/hash)
+ (only-in :std/hash/misc hash-remove!)
+ :poo-flow/core/poo-clos/interface
+ (only-in :poo-flow/core/contribution/verification
+ poo-flow-verification-adapter poo-flow-verify
+ poo-flow-verification-valid?
+ poo-flow-revoke-verification!)
+ (only-in :poo-flow/src/feature-system/source-lock-feature
+ source-lock-payload-digest)
+ (only-in :poo-flow/modules/authorization/providers/cedar/interface
+ poo-flow-cedar-runtime-handoff?
+ poo-flow-cedar-authorization-request
+ poo-flow-cedar-authorization-request?)
+ (only-in :poo-flow/lambda-aitia/modules/assurance/types
+ assurance-snapshot? assurance-artifact? assurance-claim?
+ assurance-obligation?
+ assurance-decision? assurance-decision-preflight?
+ AssuranceRequiredSupport AssessmentRequiredSupport
+ AssuranceDecisionPreflight)
+ (only-in :poo-flow/lambda-aitia/modules/assurance/funs
+ assurance-snapshot-canonical? assurance-node-canonical
+ assurance-canonical-digest)
+ (only-in :poo-flow/lambda-aitia/modules/assurance/evidence-admission
+ assurance-evaluate-evidence-admission
+ assurance-evidence-outcome-bound?
+ assurance-verification-subject
+ assurance-snapshot-semantic-digest
+ assurance-verification-subject-snapshot
+ assurance-candidate-support-structural?
+ assurance-support-sealed-under-adapter?))
+
+(export assurance-verification-host assurance-host-verify
+ assurance-host-sealed-support? assurance-host-revoke!
+ assurance-host-admit assurance-host-admit-verified
+ assurance-host-verification-current? assurance-host-admission-current?
+ assurance-host-source-current?
+ assurance-host-admitted-support?
+ assurance-host-required-support
+ assurance-host-preflight-decision
+ assurance-host-decision-preflight-current?
+ assurance-host-cedar-request assurance-host-cedar-request-current?
+ assurance-host-revoke-decision-preflight!
+ assurance-host-revoke-admission!)
+
+(def issued-hosts (make-hash-table-eq weak-keys: #t))
+(def issued-host-receipts (make-hash-table-eq weak-keys: #t))
+(def issued-admissions (make-hash-table-eq weak-keys: #t))
+(def issued-decision-preflights (make-hash-table-eq weak-keys: #t))
+(def issued-cedar-requests (make-hash-table-eq weak-keys: #t))
+
+(def (host-entry host)
+ (let (entry (hash-get issued-hosts host))
+ (and entry (object? host) (.slot? host 'identity)
+ (equal? (.ref host 'identity) (vector-ref entry 0))
+ entry)))
+
+(def (required-host-entry host)
+ (or (host-entry host)
+ (error "unissued or modified assurance verification Host")))
+
+(def (host-instant entry)
+ (let (now ((vector-ref entry 2)))
+ (unless (and (exact-integer? now) (>= now 0))
+ (error "assurance Host clock must return a nonnegative instant"))
+ (when (< now (vector-ref entry 4))
+ (error "assurance Host clock moved backwards"))
+ (vector-set! entry 4 now)
+ now))
+
+(def (invalidate-host-cut! entry)
+ ;; A weak receipt index avoids retaining every old seal for the Host's life.
+ ;; The epoch blocks all prior seals at this Host even if the old cut returns.
+ (vector-set! entry 8 (+ (vector-ref entry 8) 1)))
+
+(def (host-current-snapshot entry)
+ (let (snapshot ((vector-ref entry 5)))
+ (unless (and (assurance-snapshot? snapshot)
+ (assurance-snapshot-canonical? snapshot))
+ (invalidate-host-cut! entry)
+ (error "assurance Host snapshot source returned a noncanonical snapshot"))
+ (let ((current-digest (assurance-snapshot-semantic-digest snapshot))
+ (previous-digest (vector-ref entry 7)))
+ (when (and previous-digest
+ (not (equal? previous-digest current-digest)))
+ (invalidate-host-cut! entry))
+ (vector-set! entry 7 (string-copy current-digest)))
+ snapshot))
+
+(def (host-snapshot-current? entry snapshot obligation evidence outcome)
+ (equal?
+ (assurance-verification-subject-snapshot
+ (assurance-verification-subject snapshot obligation evidence outcome))
+ (assurance-verification-subject-snapshot
+ (assurance-verification-subject
+ (host-current-snapshot entry) obligation evidence outcome))))
+
+(def (assurance-verification-host
+ identity-value operation clock lease snapshot-source
+ source-reader: (source-reader #f))
+ (unless (and (string? identity-value)
+ (> (string-length identity-value) 0)
+ (procedure? operation) (procedure? clock)
+ (exact-integer? lease) (> lease 0)
+ (procedure? snapshot-source)
+ (or (not source-reader) (procedure? source-reader)))
+ (error "invalid assurance verification Host configuration"))
+ (let* ((host (.o identity: (string-copy identity-value)))
+ (adapter
+ (poo-flow-verification-adapter
+ identity-value
+ (lambda (subject now until)
+ (if source-reader
+ (let (inputs
+ (host-materialized-inputs
+ host (.ref subject 'snapshot) (.ref subject 'outcome)))
+ (and inputs
+ (let (expected
+ (map (lambda (input)
+ (vector (string-copy (.ref input 'identity))
+ (string-copy (.ref input 'revision))
+ (string-copy (.ref input 'digest))))
+ inputs))
+ (and (eq? (operation subject now until inputs) #t)
+ (host-handed-inputs-current? inputs expected)))))
+ (operation subject now until)))
+ assurance-verification-subject-snapshot)))
+ (hash-put! issued-hosts host
+ (vector (string-copy identity-value) adapter clock lease -1
+ snapshot-source 0 #f 0 source-reader))
+ host))
+
+;;; An eligible report alone is never an issued seal. The configured operation
+;;; must independently return #t over the complete frozen semantic subject.
+(def (assurance-host-verify host snapshot obligation evidence outcome)
+ (let* ((entry (required-host-entry host))
+ (eligibility
+ (assurance-evaluate-evidence-admission snapshot obligation outcome)))
+ (and (.ref eligibility 'eligible?)
+ (assurance-evidence-outcome-bound? snapshot evidence outcome)
+ (host-snapshot-current? entry snapshot obligation evidence outcome)
+ (host-outcome-inputs-current? host snapshot outcome)
+ (let (now (host-instant entry))
+ (let (receipt
+ (poo-flow-verify
+ (vector-ref entry 1)
+ (assurance-verification-subject
+ snapshot obligation evidence outcome)
+ now (+ now (vector-ref entry 3))))
+ (and receipt
+ (if (host-outcome-inputs-current? host snapshot outcome)
+ (begin
+ (hash-put! issued-host-receipts receipt
+ (vector host (vector-ref entry 8)))
+ receipt)
+ (begin
+ (poo-flow-revoke-verification! (vector-ref entry 1)
+ receipt)
+ #f))))))))
+
+;;; This checks a Host-issued seal; it does not promote evidence or grant an effect.
+(def (assurance-host-sealed-support?
+ host relation evidence obligation snapshot outcome receipt)
+ (let (entry (required-host-entry host))
+ (let (issued (hash-get issued-host-receipts receipt))
+ (and issued (eq? host (vector-ref issued 0))
+ (host-snapshot-current? entry snapshot obligation evidence outcome)
+ (host-outcome-inputs-current? host snapshot outcome)
+ (= (vector-ref issued 1) (vector-ref entry 8))
+ (assurance-support-sealed-under-adapter?
+ relation evidence obligation snapshot outcome
+ (vector-ref entry 1) receipt (host-instant entry))))))
+
+(def (assurance-host-revoke! host receipt)
+ (let* ((entry (required-host-entry host))
+ (issued (hash-get issued-host-receipts receipt)))
+ (unless (and issued (eq? host (vector-ref issued 0)))
+ (error "receipt was not issued by this assurance Host"))
+ (hash-remove! issued-host-receipts receipt)
+ (poo-flow-revoke-verification! (vector-ref entry 1) receipt)))
+
+;;; Admission is a separate Host-issued capability over the candidate cut.
+;;; It does not mutate AssuranceEvidence or the snapshot that the outcome names.
+(def (assurance-host-admit host obligation evidence outcome)
+ (let* ((entry (required-host-entry host))
+ (snapshot (host-current-snapshot entry)))
+ (and (assurance-evidence-outcome-bound? snapshot evidence outcome)
+ (eq? (.ref evidence 'admission-state) 'candidate)
+ (let (receipt (assurance-host-verify host snapshot obligation evidence outcome))
+ (and receipt
+ (assurance-host-admit-verified host obligation evidence outcome receipt))))))
+
+;;; Verify and Admit can be separate lifecycle stages. Admission consumes an
+;;; exact current Host-issued seal; it must not execute a verifier again or
+;;; refresh an expired seal from a submitted success report.
+(def (assurance-host-verification-current? host receipt snapshot obligation evidence outcome)
+ (let* ((entry (required-host-entry host))
+ (issued (hash-get issued-host-receipts receipt)))
+ (and issued (eq? host (vector-ref issued 0))
+ (host-snapshot-current? entry snapshot obligation evidence outcome)
+ (= (vector-ref issued 1) (vector-ref entry 8))
+ (host-outcome-inputs-current? host snapshot outcome)
+ (assurance-evidence-outcome-bound? snapshot evidence outcome)
+ (poo-flow-verification-valid?
+ (vector-ref entry 1) receipt
+ (assurance-verification-subject snapshot obligation evidence outcome)
+ (host-instant entry))
+ (= (vector-ref issued 1) (vector-ref entry 8)))))
+
+(def (assurance-host-admit-verified host obligation evidence outcome receipt)
+ (let* ((entry (required-host-entry host))
+ (snapshot (host-current-snapshot entry)))
+ (and (assurance-evidence-outcome-bound? snapshot evidence outcome)
+ (eq? (.ref evidence 'admission-state) 'candidate)
+ (assurance-host-verification-current?
+ host receipt snapshot obligation evidence outcome)
+ (if (host-snapshot-current? entry snapshot obligation evidence outcome)
+ (let* ((next (+ (vector-ref entry 6) 1))
+ (identity-value
+ (string-append (vector-ref entry 0) "/admission/" (number->string next)))
+ (subject-digest-value
+ (assurance-verification-subject-snapshot
+ (assurance-verification-subject snapshot obligation evidence outcome)))
+ (issued-at-value (.ref receipt 'issued-at))
+ (expires-at-value (.ref receipt 'expires-at))
+ (admission
+ (.o identity: identity-value issuer: (vector-ref entry 0)
+ issued-at: issued-at-value expires-at: expires-at-value
+ subject-digest: subject-digest-value)))
+ (vector-set! entry 6 next)
+ (hash-put! issued-admissions admission
+ (vector host receipt identity-value (vector-ref entry 0)
+ issued-at-value expires-at-value subject-digest-value
+ obligation evidence outcome (vector-ref entry 8)))
+ admission)
+ (begin (assurance-host-revoke! host receipt) #f)))))
+
+(def (admission-entry host admission)
+ (let (entry (hash-get issued-admissions admission))
+ (and entry (eq? host (vector-ref entry 0))
+ (object? admission)
+ (.slot? admission 'identity) (.slot? admission 'issuer)
+ (.slot? admission 'issued-at) (.slot? admission 'expires-at)
+ (.slot? admission 'subject-digest)
+ (equal? (.ref admission 'identity) (vector-ref entry 2))
+ (equal? (.ref admission 'issuer) (vector-ref entry 3))
+ (equal? (.ref admission 'issued-at) (vector-ref entry 4))
+ (equal? (.ref admission 'expires-at) (vector-ref entry 5))
+ (equal? (.ref admission 'subject-digest) (vector-ref entry 6))
+ entry)))
+
+(def (current-admission-snapshot host-state issued)
+ (and issued
+ (let* ((now (host-instant host-state))
+ (snapshot (host-current-snapshot host-state))
+ (obligation (vector-ref issued 7))
+ (evidence (vector-ref issued 8))
+ (outcome (vector-ref issued 9))
+ (eligibility
+ (assurance-evaluate-evidence-admission
+ snapshot obligation outcome))
+ (subject
+ (assurance-verification-subject
+ snapshot obligation evidence outcome)))
+ (and (= (vector-ref issued 10) (vector-ref host-state 8))
+ (<= (vector-ref issued 4) now)
+ (< now (vector-ref issued 5))
+ (.ref eligibility 'eligible?)
+ (assurance-evidence-outcome-bound?
+ snapshot evidence outcome)
+ (host-outcome-inputs-current?
+ (vector-ref issued 0) snapshot outcome)
+ (eq? (.ref evidence 'admission-state) 'candidate)
+ (equal? (assurance-verification-subject-snapshot subject)
+ (vector-ref issued 6))
+ (poo-flow-verification-valid?
+ (vector-ref host-state 1) (vector-ref issued 1)
+ subject now)
+ snapshot))))
+
+(def (assurance-host-admission-current? host admission)
+ (let (host-state (required-host-entry host))
+ (and (current-admission-snapshot
+ host-state (admission-entry host admission)) #t)))
+
+;;; Consume an exact Host admission as one current assurance-edge witness.
+;;; This does not close the claim's full support set or authorize a Decision.
+(def (assurance-host-admitted-support? host relation admission)
+ (let* ((host-state (required-host-entry host))
+ (issued (admission-entry host admission)))
+ (and issued
+ (let (snapshot (current-admission-snapshot host-state issued))
+ (and snapshot
+ (assurance-candidate-support-structural?
+ relation (vector-ref issued 8) (vector-ref issued 7)
+ snapshot))))))
+
+(def (snapshot-node snapshot identity)
+ (find (lambda (node) (equal? (.ref node 'identity) identity))
+ (.ref snapshot 'nodes)))
+
+;;; Source bytes come only from the Host's configured reader. This is a
+;;; conditional application trust boundary, not a claim about Git provenance
+;;; or the verifier's actual process inputs. Re-read and recheck the cut after
+;;; materialization so a moving snapshot cannot qualify an old source.
+(def (host-source-payload host snapshot artifact)
+ (let* ((entry (required-host-entry host))
+ (reader (vector-ref entry 9)))
+ (and reader
+ (assurance-snapshot? snapshot)
+ (assurance-snapshot-canonical? snapshot)
+ (assurance-artifact? artifact)
+ (let* ((current (host-current-snapshot entry))
+ (expected-cut (assurance-snapshot-semantic-digest snapshot))
+ (current-artifact
+ (snapshot-node current (.ref artifact 'identity)))
+ (revision (assoc (.ref artifact 'identity)
+ (.ref current 'source-revisions))))
+ (and (equal? expected-cut
+ (assurance-snapshot-semantic-digest current))
+ current-artifact
+ (assurance-artifact? current-artifact)
+ (equal? (assurance-node-canonical current-artifact)
+ (assurance-node-canonical artifact))
+ revision
+ (equal? (cdr revision) (.ref artifact 'revision))
+ (eq? (.ref artifact 'state) 'supported)
+ (let (payload (reader (.ref artifact 'identity)))
+ (let (matches?
+ (and (or (string? payload) (u8vector? payload))
+ (equal? (source-lock-payload-digest payload)
+ (.ref artifact 'content-digest))))
+ (unless matches? (invalidate-host-cut! entry))
+ (and matches?
+ (equal? expected-cut
+ (assurance-snapshot-semantic-digest
+ (host-current-snapshot entry)))
+ payload))))))))
+
+(def (assurance-host-source-current? host snapshot artifact)
+ (and (host-source-payload host snapshot artifact) #t))
+
+;;; The configured operation receives these POO values as its fourth argument
+;;; when source-reader: is present. The Host, not the operation, selects and
+;;; validates the bytes. It rehashes the handed values after the operation.
+(def (host-materialized-inputs host snapshot outcome)
+ (let loop ((remaining (.ref outcome 'inputs)) (collected '()))
+ (if (null? remaining)
+ (reverse collected)
+ (let* ((input (car remaining))
+ (artifact (snapshot-node snapshot (.ref input 'identity)))
+ (payload-value (and artifact
+ (host-source-payload host snapshot artifact)))
+ (handed-payload
+ (and payload-value
+ (if (string? payload-value)
+ (string-copy payload-value)
+ (u8vector-copy payload-value)))))
+ (and payload-value
+ (loop (cdr remaining)
+ (cons (.o identity: (.ref input 'identity)
+ revision: (.ref input 'revision)
+ digest: (.ref input 'digest)
+ payload: handed-payload)
+ collected)))))))
+
+(def (host-handed-inputs-current? inputs expected)
+ (let loop ((remaining inputs) (original expected))
+ (cond
+ ((null? remaining) (null? original))
+ ((null? original) #f)
+ (else
+ (let ((input (car remaining)) (fields (car original)))
+ (and (equal? (.ref input 'identity) (vector-ref fields 0))
+ (equal? (.ref input 'revision) (vector-ref fields 1))
+ (equal? (.ref input 'digest) (vector-ref fields 2))
+ (let (payload-value (.ref input 'payload))
+ (and (or (string? payload-value) (u8vector? payload-value))
+ (equal? (source-lock-payload-digest payload-value)
+ (vector-ref fields 2))))
+ (loop (cdr remaining) (cdr original))))))))
+
+;;; An optional Host source reader strengthens issuance and currentness for
+;;; every declared verifier input. With no configured reader the old Host
+;;; remains a verification Host, but it cannot claim source-byte checking.
+(def (host-outcome-inputs-current? host snapshot outcome)
+ (let (entry (required-host-entry host))
+ (or (not (vector-ref entry 9))
+ (every (lambda (input)
+ (let (artifact (snapshot-node snapshot
+ (.ref input 'identity)))
+ (and artifact
+ (assurance-host-source-current?
+ host snapshot artifact))))
+ (.ref outcome 'inputs)))))
+
+(def (required-support-link? snapshot obligation claim)
+ (find (lambda (relation)
+ (and (eq? (.ref relation 'plane) 'assurance)
+ (eq? (.ref relation 'relation) 'supports)
+ (equal? (.ref relation 'source) (.ref obligation 'identity))
+ (equal? (.ref relation 'target) (.ref claim 'identity))
+ (not (memq (.ref relation 'modality)
+ '(hypothesized counterfactual)))))
+ (.ref snapshot 'relations)))
+
+(def (admitted-requirement-blocker host host-state snapshot obligation admissions)
+ (let* ((matching
+ (filter (lambda (admission)
+ (let (issued (admission-entry host admission))
+ (and issued
+ (equal? (.ref (vector-ref issued 7) 'identity)
+ (.ref obligation 'identity)))))
+ admissions)))
+ (if (null? matching)
+ 'admission-missing
+ (let loop ((remaining matching) (saw-current? #f))
+ (if (null? remaining)
+ (if saw-current? 'evidence-link-missing 'admission-invalid)
+ (let* ((issued (admission-entry host (car remaining)))
+ (current (current-admission-snapshot host-state issued)))
+ (if (and current
+ (equal? (assurance-snapshot-semantic-digest current)
+ (assurance-snapshot-semantic-digest snapshot)))
+ (if (find (lambda (relation)
+ (assurance-candidate-support-structural?
+ relation (vector-ref issued 8) obligation snapshot))
+ (.ref snapshot 'relations))
+ #f
+ (loop (cdr remaining) #t))
+ (loop (cdr remaining) saw-current?))))))))
+
+(def (required-support-row host host-state snapshot claim-value identity admissions)
+ (let* ((obligation (snapshot-node snapshot identity))
+ (reason
+ (cond
+ ((not (and obligation (assurance-obligation? obligation)))
+ 'obligation-missing)
+ ((or (not (equal? (.ref obligation 'claim) (.ref claim-value 'identity)))
+ (not (equal? (.ref obligation 'subject) (.ref claim-value 'subject)))
+ (not (equal? (.ref obligation 'scope) (.ref claim-value 'scope)))
+ (not (equal? (.ref obligation 'snapshot)
+ (.ref snapshot 'identity)))
+ (not (equal? (.ref obligation 'snapshot-revision)
+ (.ref snapshot 'revision)))
+ (not (equal? (.ref obligation 'snapshot-context-digest)
+ (.ref snapshot 'context-digest)))
+ (memq (.ref obligation 'state)
+ '(violated stale conflicted hypothesized counterfactual
+ not-applicable)))
+ 'obligation-stale)
+ ((not (required-support-link? snapshot obligation claim-value))
+ 'support-link-missing)
+ (else
+ (admitted-requirement-blocker
+ host host-state snapshot obligation admissions)))))
+ (poo-clos-check-model
+ AssessmentRequiredSupport
+ (.o (:: @ (poo-clos-model-prototype AssessmentRequiredSupport))
+ obligation: identity supported?: (not reason) blocker: reason))))
+
+;;; Inert, explainable input for a later Decision evaluator. Completion means
+;;; only that every declared support requirement has a current Host witness;
+;;; it does not resolve policy, validity intervals or Runtime effect authority.
+(def (assurance-host-required-support host claim-value admissions)
+ (unless (and (assurance-claim? claim-value) (list? admissions))
+ (error "invalid required-support inputs"))
+ (let* ((host-state (required-host-entry host))
+ (snapshot (host-current-snapshot host-state))
+ (initial-epoch (vector-ref host-state 8))
+ (current-claim (snapshot-node snapshot (.ref claim-value 'identity)))
+ (required-identities (.ref claim-value 'support-requirements))
+ (rows
+ (map (lambda (identity)
+ (required-support-row
+ host host-state snapshot claim-value identity admissions))
+ required-identities))
+ (global-blockers
+ (append
+ (if (pair? (.ref snapshot 'conflicts)) '(snapshot-conflicted) '())
+ (if (pair? (.ref snapshot 'unresolved)) '(snapshot-unresolved) '())
+ (if (and current-claim (assurance-claim? current-claim)
+ (equal? (assurance-node-canonical claim-value)
+ (assurance-node-canonical current-claim))
+ (member (cons (.ref claim-value 'identity)
+ (.ref claim-value 'revision))
+ (.ref snapshot 'claim-revisions)))
+ '() '(claim-not-current))
+ (if (null? required-identities) '(no-requirements) '())
+ (if (= (length required-identities)
+ (length (delete-duplicates/hash required-identities)))
+ '() '(duplicate-requirements))
+ (if (pair? (.ref claim-value 'assumptions))
+ '(assumptions-unresolved) '())
+ (if (pair? (.ref claim-value 'defeaters))
+ '(defeaters-unresolved) '())
+ (if (memq (.ref claim-value 'state)
+ '(violated stale conflicted hypothesized counterfactual
+ not-applicable))
+ '(claim-state-blocked) '())
+ (if (find (lambda (node)
+ (and (memq (.ref node 'kind)
+ '(counterexample finding))
+ (equal? (.ref node 'challenges)
+ (.ref claim-value 'identity))))
+ (.ref snapshot 'nodes))
+ '(challenge-unresolved) '())))
+ (final-snapshot (host-current-snapshot host-state))
+ (final-blockers
+ (append global-blockers
+ (if (and (= initial-epoch (vector-ref host-state 8))
+ (equal?
+ (assurance-snapshot-semantic-digest snapshot)
+ (assurance-snapshot-semantic-digest final-snapshot)))
+ '() '(source-changed)))))
+ (poo-clos-check-model
+ AssuranceRequiredSupport
+ (.o (:: @ (poo-clos-model-prototype AssuranceRequiredSupport))
+ schema: "lambda-aitia.required-support"
+ claim: (.ref claim-value 'identity)
+ snapshot-digest: (.ref snapshot 'digest)
+ requirements: rows blockers: final-blockers
+ complete?: (and (null? final-blockers)
+ (pair? rows)
+ (every (lambda (row) (.ref row 'supported?)) rows))
+ release-authorized?: #f))))
+
+(def (decision-depends-on-claim? snapshot decision-value claim-value)
+ (find (lambda (relation)
+ (and (eq? (.ref relation 'plane) 'structural)
+ (eq? (.ref relation 'relation) 'depends-on)
+ (equal? (.ref relation 'source)
+ (.ref decision-value 'identity))
+ (equal? (.ref relation 'target)
+ (.ref claim-value 'identity))
+ (not (memq (.ref relation 'modality)
+ '(hypothesized counterfactual)))))
+ (.ref snapshot 'relations)))
+
+(def (decision-preflight-bound? snapshot decision-value claim-value)
+ (and (assurance-decision? decision-value)
+ (assurance-claim? claim-value)
+ (let ((current-decision
+ (snapshot-node snapshot (.ref decision-value 'identity)))
+ (current-claim
+ (snapshot-node snapshot (.ref claim-value 'identity))))
+ (and current-decision (assurance-decision? current-decision)
+ current-claim (assurance-claim? current-claim)
+ (equal? (assurance-node-canonical current-decision)
+ (assurance-node-canonical decision-value))
+ (equal? (assurance-node-canonical current-claim)
+ (assurance-node-canonical claim-value))))
+ (eq? (.ref decision-value 'outcome) 'unknown)
+ (eq? (.ref decision-value 'state) 'unknown)
+ (equal? (.ref decision-value 'subject)
+ (.ref claim-value 'subject))
+ (equal? (.ref decision-value 'snapshot)
+ (.ref snapshot 'identity))
+ (equal? (.ref decision-value 'policy)
+ (.ref snapshot 'policy-identity))
+ (decision-depends-on-claim? snapshot decision-value claim-value)))
+
+(def (same-host-cut? host-state snapshot-value epoch-value)
+ (and (= epoch-value (vector-ref host-state 8))
+ (equal? (assurance-snapshot-semantic-digest snapshot-value)
+ (assurance-snapshot-semantic-digest
+ (host-current-snapshot host-state)))
+ (= epoch-value (vector-ref host-state 8))))
+
+;;; This capability attests a currently bound *undecided* Decision candidate.
+;;; It cannot be projected as Cedar permit or Runtime effect authorization.
+(def (assurance-host-preflight-decision
+ host decision-value claim-value admissions)
+ (unless (list? admissions)
+ (error "Decision preflight requires an admission list"))
+ (let* ((host-state (required-host-entry host))
+ (snapshot-value (host-current-snapshot host-state))
+ (epoch-value (vector-ref host-state 8)))
+ (and (decision-preflight-bound?
+ snapshot-value decision-value claim-value)
+ (.ref (assurance-host-required-support
+ host claim-value admissions)
+ 'complete?)
+ (same-host-cut? host-state snapshot-value epoch-value)
+ (let* ((next (+ (vector-ref host-state 6) 1))
+ (identity-value
+ (string-append (vector-ref host-state 0) "/decision-preflight/"
+ (number->string next)))
+ (issuer-value (vector-ref host-state 0))
+ (decision-identity-value (.ref decision-value 'identity))
+ (claim-identity-value (.ref claim-value 'identity))
+ (snapshot-digest-value (.ref snapshot-value 'digest))
+ (semantic-digest-value
+ (assurance-snapshot-semantic-digest snapshot-value))
+ (preflight
+ (poo-clos-check-model
+ AssuranceDecisionPreflight
+ (.o (:: @ (poo-clos-model-prototype
+ AssuranceDecisionPreflight))
+ schema: "lambda-aitia.decision-preflight"
+ identity: identity-value issuer: issuer-value
+ decision: decision-identity-value
+ claim: claim-identity-value
+ snapshot-digest: snapshot-digest-value
+ release-authorized?: #f))))
+ (vector-set! host-state 6 next)
+ (hash-put! issued-decision-preflights preflight
+ (vector host identity-value issuer-value
+ decision-identity-value claim-identity-value
+ snapshot-digest-value semantic-digest-value
+ epoch-value decision-value claim-value
+ admissions))
+ preflight))))
+
+(def (decision-preflight-entry host preflight)
+ (let (issued (hash-get issued-decision-preflights preflight))
+ (and issued (eq? host (vector-ref issued 0))
+ (assurance-decision-preflight? preflight)
+ (equal? (.ref preflight 'identity) (vector-ref issued 1))
+ (equal? (.ref preflight 'issuer) (vector-ref issued 2))
+ (equal? (.ref preflight 'decision) (vector-ref issued 3))
+ (equal? (.ref preflight 'claim) (vector-ref issued 4))
+ (equal? (.ref preflight 'snapshot-digest) (vector-ref issued 5))
+ issued)))
+
+(def (assurance-host-decision-preflight-current? host preflight)
+ (let* ((host-state (required-host-entry host))
+ (issued (decision-preflight-entry host preflight)))
+ (and issued
+ (= (vector-ref issued 7) (vector-ref host-state 8))
+ (let (snapshot-value (host-current-snapshot host-state))
+ (and (= (vector-ref issued 7) (vector-ref host-state 8))
+ (equal? (.ref snapshot-value 'digest)
+ (vector-ref issued 5))
+ (equal? (assurance-snapshot-semantic-digest snapshot-value)
+ (vector-ref issued 6))
+ (decision-preflight-bound?
+ snapshot-value (vector-ref issued 8) (vector-ref issued 9))
+ (.ref (assurance-host-required-support
+ host (vector-ref issued 9) (vector-ref issued 10))
+ 'complete?)
+ (same-host-cut?
+ host-state snapshot-value (vector-ref issued 7)))))))
+
+;;; Project a current preflight into POO Flow's inert Cedar request family.
+;;; The request is editable input, not a Host seal, Cedar outcome or effect grant.
+;;; The Runtime Host must independently validate the current source cut at use.
+(def (cedar-request-fingerprint request)
+ ;; Freeze a value digest, not aliases to caller-visible mutable strings.
+ (assurance-canonical-digest
+ (list (.ref request 'principal) (.ref request 'action)
+ (.ref request 'resource) (.alist (.ref request 'context))
+ (.ref request 'intent) (.alist (.ref request 'handoff)))))
+
+(def (assurance-host-cedar-request host preflight action-value intent-value
+ handoff-value)
+ (unless (poo-flow-cedar-runtime-handoff? handoff-value)
+ (error "Decision Cedar projection requires a POO Flow runtime handoff"))
+ (let* ((host-state (required-host-entry host))
+ (issued (decision-preflight-entry host preflight)))
+ (and issued
+ (assurance-host-decision-preflight-current? host preflight)
+ (let* ((snapshot-value (host-current-snapshot host-state))
+ (epoch-value (vector-ref host-state 8))
+ (decision-value (vector-ref issued 8))
+ (claim-value (vector-ref issued 9))
+ (context-value
+ (.o aitia_schema: "lambda-aitia.cedar-preflight.v1"
+ poo_flow_preflight_only: #t
+ aitia_preflight: (vector-ref issued 1)
+ aitia_decision: (vector-ref issued 3)
+ aitia_claim: (vector-ref issued 4)
+ aitia_snapshot_digest: (vector-ref issued 5)
+ aitia_snapshot_semantic_digest: (vector-ref issued 6)
+ aitia_policy: (.ref decision-value 'policy)
+ aitia_policy_revision: (.ref snapshot-value 'policy-revision)
+ aitia_preflight_only: #t))
+ (request
+ (poo-flow-cedar-authorization-request
+ (.ref decision-value 'authority) action-value
+ (.ref claim-value 'subject) context-value intent-value
+ handoff-value)))
+ (and (= epoch-value (vector-ref issued 7))
+ (same-host-cut? host-state snapshot-value epoch-value)
+ (assurance-host-decision-preflight-current? host preflight)
+ (begin
+ (hash-put! issued-cedar-requests request
+ (vector host preflight epoch-value
+ (cedar-request-fingerprint request)))
+ request))))))
+
+;;; Source-owned use-time check for the exact Host-issued request. This does
+;;; not evaluate Cedar or permit an effect. A future Runtime integration must
+;;; call it at use and retain authorization and effect-handoff ownership.
+(def (assurance-host-cedar-request-current? host request)
+ (let* ((host-state (required-host-entry host))
+ (issued (hash-get issued-cedar-requests request)))
+ (and issued
+ (eq? host (vector-ref issued 0))
+ (poo-flow-cedar-authorization-request? request)
+ (object? (.ref request 'context))
+ (poo-flow-cedar-runtime-handoff? (.ref request 'handoff))
+ (= (vector-ref issued 2) (vector-ref host-state 8))
+ (equal? (cedar-request-fingerprint request) (vector-ref issued 3))
+ (assurance-host-decision-preflight-current?
+ host (vector-ref issued 1))
+ (= (vector-ref issued 2) (vector-ref host-state 8)))))
+
+(def (assurance-host-revoke-decision-preflight! host preflight)
+ (required-host-entry host)
+ (unless (decision-preflight-entry host preflight)
+ (error "unissued or modified assurance Decision preflight"))
+ (hash-remove! issued-decision-preflights preflight))
+
+(def (assurance-host-revoke-admission! host admission)
+ (required-host-entry host)
+ (let (issued (admission-entry host admission))
+ (unless issued (error "unissued or modified assurance admission"))
+ (assurance-host-revoke! host (vector-ref issued 1))
+ (hash-remove! issued-admissions admission)))
diff --git a/modules/assurance/interface.ss b/modules/assurance/interface.ss
index cfe8620..286ed2d 100644
--- a/modules/assurance/interface.ss
+++ b/modules/assurance/interface.ss
@@ -9,5 +9,11 @@
(export (import: :poo-flow/lambda-aitia/modules/assurance/objects))
(import :poo-flow/lambda-aitia/modules/assurance/funs)
(export (import: :poo-flow/lambda-aitia/modules/assurance/funs))
+(import :poo-flow/lambda-aitia/modules/assurance/evidence-admission)
+(export (import: :poo-flow/lambda-aitia/modules/assurance/evidence-admission))
+(import :poo-flow/lambda-aitia/modules/assurance/host)
+(export (import: :poo-flow/lambda-aitia/modules/assurance/host))
+(import :poo-flow/lambda-aitia/modules/assurance/effect-host)
+(export (import: :poo-flow/lambda-aitia/modules/assurance/effect-host))
(import :poo-flow/lambda-aitia/modules/assurance/config)
(export (import: :poo-flow/lambda-aitia/modules/assurance/config))
diff --git a/modules/assurance/invalidation-projection.ss b/modules/assurance/invalidation-projection.ss
index 8792612..b9f8091 100644
--- a/modules/assurance/invalidation-projection.ss
+++ b/modules/assurance/invalidation-projection.ss
@@ -12,7 +12,7 @@
poo-flow-graph-loop-analysis-receipt)
(only-in :poo-flow/src/graph/types
poo-flow-graph poo-flow-graph-edge poo-flow-graph-node)
- (only-in :poo-flow/src/modules/temporal-causality/funs
+ (only-in :poo-flow/modules/temporal-causality/funs
poo-flow-structural-impact-analyze)
(only-in :gerbil/core list-sort)
:std/list/list
@@ -21,7 +21,7 @@
(export assurance-invalidation-graph assurance-invalidation-analysis)
(def +assurance-invalidation-relations+
- '(depends-on implements tests refines supports discharges
+ '(depends-on composes implements tests refines supports discharges
invalidates defeats authorizes denies requires-review))
(def (invalidation-graph-id identity)
@@ -31,7 +31,7 @@
(let ((plane (.ref relation 'plane)) (kind (.ref relation 'relation)))
(cond
((and (eq? plane 'structural)
- (memq kind '(depends-on implements tests refines)))
+ (memq kind '(depends-on composes implements tests refines)))
(cons (.ref relation 'target) (.ref relation 'source)))
((and (eq? plane 'assurance) (memq kind '(supports discharges)))
(cons (.ref relation 'target) (.ref relation 'source)))
diff --git a/modules/assurance/objects.ss b/modules/assurance/objects.ss
index a3c57f8..a570743 100644
--- a/modules/assurance/objects.ss
+++ b/modules/assurance/objects.ss
@@ -4,20 +4,22 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import (only-in :clan/poo/object .o)
- :poo-flow/src/module-system/contribution/model
+ (only-in :gerbil/core list-sort)
+ :std/list/list
+ :poo-flow/core/poo-clos/interface
:poo-flow/lambda-aitia/modules/assurance/types)
(export assurance-artifact assurance-claim assurance-assumption
assurance-observation assurance-event assurance-action
assurance-obligation assurance-evidence assurance-counterexample
assurance-finding assurance-decision assurance-effect
- assurance-relation)
+ assurance-relation assurance-verifier-candidate)
(def (assurance-artifact identity-value revision-value state-value digest-value
owner: owner-value media-kind: media-kind-value
provenance: (provenance-values '()))
- (poo-flow-check-model AssuranceArtifact
- (.o (:: @ (poo-flow-model-prototype AssuranceArtifact))
+ (poo-clos-check-model AssuranceArtifact
+ (.o (:: @ (poo-clos-model-prototype AssuranceArtifact))
identity: identity-value kind: 'artifact revision: revision-value
state: state-value content-digest: digest-value owner: owner-value
media-kind: media-kind-value provenance: provenance-values)))
@@ -28,8 +30,8 @@
support-requirements: (support-values '())
scope: scope-value valid-from: valid-from-value
valid-until: (valid-until-value #f))
- (poo-flow-check-model AssuranceClaim
- (.o (:: @ (poo-flow-model-prototype AssuranceClaim))
+ (poo-clos-check-model AssuranceClaim
+ (.o (:: @ (poo-clos-model-prototype AssuranceClaim))
identity: identity-value kind: 'claim revision: revision-value
state: state-value content-digest: digest-value subject: subject-value
predicate: predicate-value assumptions: assumption-values
@@ -40,8 +42,8 @@
owner: owner-value scope: scope-value
review-policy: review-policy-value
expires-at: (expires-at-value #f))
- (poo-flow-check-model AssuranceAssumption
- (.o (:: @ (poo-flow-model-prototype AssuranceAssumption))
+ (poo-clos-check-model AssuranceAssumption
+ (.o (:: @ (poo-clos-model-prototype AssuranceAssumption))
identity: identity-value kind: 'assumption revision: revision-value
state: state-value content-digest: digest-value owner: owner-value
scope: scope-value review-policy: review-policy-value
@@ -50,8 +52,8 @@
subject: subject-value source: source-value
clock-role: clock-role-value
logical-position: logical-position-value)
- (poo-flow-check-model AssuranceObservation
- (.o (:: @ (poo-flow-model-prototype AssuranceObservation))
+ (poo-clos-check-model AssuranceObservation
+ (.o (:: @ (poo-clos-model-prototype AssuranceObservation))
identity: identity-value kind: 'observation revision: revision-value
state: state-value content-digest: digest-value subject: subject-value
source: source-value clock-role: clock-role-value
@@ -61,8 +63,8 @@
observation: observation-value payload-digest: payload-digest-value
modality: modality-value commitment-state: commitment-value
causal-parents: (causal-parent-values '()))
- (poo-flow-check-model AssuranceEvent
- (.o (:: @ (poo-flow-model-prototype AssuranceEvent))
+ (poo-clos-check-model AssuranceEvent
+ (.o (:: @ (poo-clos-model-prototype AssuranceEvent))
identity: identity-value kind: 'event revision: revision-value
state: state-value content-digest: digest-value subject: subject-value
event-kind: event-kind-value observation: observation-value
@@ -71,8 +73,8 @@
(def (assurance-action identity-value revision-value state-value digest-value
subject: subject-value action-kind: action-kind-value
requested-by: requested-by-value scope: scope-value)
- (poo-flow-check-model AssuranceAction
- (.o (:: @ (poo-flow-model-prototype AssuranceAction))
+ (poo-clos-check-model AssuranceAction
+ (.o (:: @ (poo-clos-model-prototype AssuranceAction))
identity: identity-value kind: 'action revision: revision-value
state: state-value content-digest: digest-value subject: subject-value
action-kind: action-kind-value requested-by: requested-by-value
@@ -80,12 +82,16 @@
(def (assurance-obligation identity-value revision-value state-value digest-value
subject: subject-value claim: claim-value
snapshot: snapshot-value evidence-kind: evidence-kind-value
- capability: capability-value scope: scope-value)
- (poo-flow-check-model AssuranceObligation
- (.o (:: @ (poo-flow-model-prototype AssuranceObligation))
+ capability: capability-value scope: scope-value
+ snapshot-revision: (snapshot-revision-value #f)
+ snapshot-context-digest: (snapshot-context-value #f))
+ (poo-clos-check-model AssuranceObligation
+ (.o (:: @ (poo-clos-model-prototype AssuranceObligation))
identity: identity-value kind: 'obligation revision: revision-value
state: state-value content-digest: digest-value subject: subject-value
claim: claim-value snapshot: snapshot-value evidence-kind: evidence-kind-value
+ snapshot-revision: snapshot-revision-value
+ snapshot-context-digest: snapshot-context-value
capability: capability-value scope: scope-value)))
(def (assurance-evidence identity-value revision-value state-value digest-value
producer: producer-value tool: tool-value
@@ -95,8 +101,8 @@
scope: scope-value valid-from: valid-from-value
valid-until: (valid-until-value #f)
admission-state: admission-state-value)
- (poo-flow-check-model AssuranceEvidence
- (.o (:: @ (poo-flow-model-prototype AssuranceEvidence))
+ (poo-clos-check-model AssuranceEvidence
+ (.o (:: @ (poo-clos-model-prototype AssuranceEvidence))
identity: identity-value kind: 'evidence revision: revision-value
state: state-value content-digest: digest-value producer: producer-value
tool: tool-value tool-version: tool-version-value
@@ -106,8 +112,8 @@
(def (assurance-counterexample identity-value revision-value state-value digest-value
subject: subject-value challenges: challenges-value
evidence: evidence-value details-digest: details-digest-value)
- (poo-flow-check-model AssuranceCounterexample
- (.o (:: @ (poo-flow-model-prototype AssuranceCounterexample))
+ (poo-clos-check-model AssuranceCounterexample
+ (.o (:: @ (poo-clos-model-prototype AssuranceCounterexample))
identity: identity-value kind: 'counterexample revision: revision-value
state: state-value content-digest: digest-value subject: subject-value
challenges: challenges-value evidence: evidence-value
@@ -115,8 +121,8 @@
(def (assurance-finding identity-value revision-value state-value digest-value
subject: subject-value challenges: challenges-value
evidence: evidence-value finding-kind: finding-kind-value)
- (poo-flow-check-model AssuranceFinding
- (.o (:: @ (poo-flow-model-prototype AssuranceFinding))
+ (poo-clos-check-model AssuranceFinding
+ (.o (:: @ (poo-clos-model-prototype AssuranceFinding))
identity: identity-value kind: 'finding revision: revision-value
state: state-value content-digest: digest-value subject: subject-value
challenges: challenges-value evidence: evidence-value
@@ -125,8 +131,8 @@
subject: subject-value snapshot: snapshot-value
policy: policy-value authority: authority-value
outcome: outcome-value)
- (poo-flow-check-model AssuranceDecision
- (.o (:: @ (poo-flow-model-prototype AssuranceDecision))
+ (poo-clos-check-model AssuranceDecision
+ (.o (:: @ (poo-clos-model-prototype AssuranceDecision))
identity: identity-value kind: 'decision revision: revision-value
state: state-value content-digest: digest-value subject: subject-value
snapshot: snapshot-value policy: policy-value authority: authority-value
@@ -134,8 +140,8 @@
(def (assurance-effect identity-value revision-value state-value digest-value
subject: subject-value decision: decision-value
effect-kind: effect-kind-value effect-state: effect-state-value)
- (poo-flow-check-model AssuranceEffect
- (.o (:: @ (poo-flow-model-prototype AssuranceEffect))
+ (poo-clos-check-model AssuranceEffect
+ (.o (:: @ (poo-clos-model-prototype AssuranceEffect))
identity: identity-value kind: 'effect revision: revision-value
state: state-value content-digest: digest-value subject: subject-value
decision: decision-value effect-kind: effect-kind-value
@@ -149,8 +155,27 @@
(unless (eq? plane-value owned-plane)
(error "assurance relation crosses its owned plane"
relation-value plane-value owned-plane))
- (poo-flow-check-model
+ (poo-clos-check-model
AssuranceRelation
- (.o (:: @ (poo-flow-model-prototype AssuranceRelation))
+ (.o (:: @ (poo-clos-model-prototype AssuranceRelation))
identity: identity-value plane: plane-value relation: relation-value
source: source-value target: target-value modality: modality-value))))
+
+(def (assurance-verifier-candidate identity-value revision-value
+ priority-value evidence-kind-values
+ capability-value)
+ (unless (and (list? evidence-kind-values)
+ (pair? evidence-kind-values)
+ (every assurance-evidence-kind? evidence-kind-values))
+ (error "invalid verifier evidence kinds" evidence-kind-values))
+ (poo-clos-check-model
+ AssuranceVerifierCandidate
+ (.o (:: @ (poo-clos-model-prototype AssuranceVerifierCandidate))
+ identity: identity-value revision: revision-value
+ priority: priority-value
+ evidence-kinds:
+ (list-sort
+ (lambda (left right)
+ (string (symbol->string left) (symbol->string right)))
+ (delete-duplicates/hash evidence-kind-values))
+ capability: capability-value)))
diff --git a/modules/assurance/types.ss b/modules/assurance/types.ss
index 800c9bc..e5c9346 100644
--- a/modules/assurance/types.ss
+++ b/modules/assurance/types.ss
@@ -4,27 +4,51 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import (only-in :clan/poo/object .o .ref .slot? object?)
- :poo-flow/src/module-system/contribution/model
+ :poo-flow/core/poo-clos/interface
:std/list/list)
(export assurance-text? assurance-digest?
assurance-node-kind? assurance-state? assurance-relation-plane?
assurance-relation-kind? assurance-relation-kind-plane
- assurance-modality?
+ assurance-modality? assurance-evidence-kind?
+assurance-node-kinds+ +assurance-states+
+assurance-relation-planes+ +assurance-modalities+
+ +assurance-evidence-kinds+
AssuranceNode AssuranceArtifact AssuranceClaim AssuranceAssumption
AssuranceObservation AssuranceEvent AssuranceAction
AssuranceObligation AssuranceEvidence AssuranceCounterexample
AssuranceFinding AssuranceDecision AssuranceEffect
AssuranceRelation AssuranceSnapshot
+ AssuranceReplacementRequirement AssuranceReplacementDerivation
+ AssuranceReplacementDeclarationReceipt
+ AssuranceVerificationPolicy AssuranceVerificationRequest
+ AssuranceVerificationPlan AssuranceVerifierCandidate
+ AssuranceVerifierChoice AssuranceVerifierChoiceReceipt
+ AssuranceVerifierInput AssuranceVerifierOutcome
+ AssuranceEvidenceAdmissionReceipt AssuranceVerificationSubject
+ AssuranceRequiredSupport AssessmentRequiredSupport
+ AssuranceDecisionPreflight
+ AssuranceCompositionRequirement AssuranceCompositionDerivation
make-assurance-invalidation-receipt-record
assurance-node? assurance-artifact? assurance-claim?
assurance-assumption? assurance-observation? assurance-event?
assurance-action? assurance-obligation? assurance-evidence?
assurance-counterexample? assurance-finding? assurance-decision?
assurance-effect? assurance-relation? assurance-snapshot?
- assurance-invalidation-receipt? assurance-invalidation-receipt-ref)
+ assurance-replacement-requirement?
+ assurance-replacement-derivation?
+ assurance-replacement-declaration-receipt?
+ assurance-invalidation-receipt? assurance-invalidation-receipt-ref
+ assurance-verification-policy? assurance-verification-request?
+ assurance-verification-plan? assurance-verifier-candidate?
+ assurance-verifier-choice? assurance-verifier-choice-receipt?
+ assurance-verifier-input? assurance-verifier-outcome?
+ assurance-evidence-admission-receipt?
+ assurance-required-support? assessment-required-support?
+ assurance-decision-preflight?
+ assurance-verification-subject?
+ assurance-composition-requirement?
+ assurance-composition-derivation?)
(def +assurance-node-kinds+
'(artifact claim assumption observation event action obligation evidence
@@ -36,6 +60,9 @@
'(provenance structural causal assurance authority))
(def +assurance-modalities+
'(observed declared derived hypothesized counterfactual))
+(def +assurance-evidence-kinds+
+ '(proof model-check replay unit-test integration-test native-test
+ static-analysis authorization-differential human-review))
(def +assurance-relation-kind-planes+
'((derived-from . provenance)
@@ -43,6 +70,7 @@
(cites . provenance)
(used . provenance)
(depends-on . structural)
+ (composes . structural)
(implements . structural)
(tests . structural)
(refines . structural)
@@ -73,6 +101,8 @@
(def (assurance-relation-plane? value)
(if (memq value +assurance-relation-planes+) #t #f))
(def (assurance-modality? value) (if (memq value +assurance-modalities+) #t #f))
+(def (assurance-evidence-kind? value)
+ (if (memq value +assurance-evidence-kinds+) #t #f))
(def (assurance-relation-kind-plane value)
(let ((entry (assq value +assurance-relation-kind-planes+)))
(and entry (cdr entry))))
@@ -84,8 +114,45 @@
(def (enum-slot name predicate)
(poo-clos-direct-slot-definition name type-predicate: predicate))
(def (maybe-text? value) (or (not value) (assurance-text? value)))
+(def (maybe-digest? value) (or (not value) (assurance-digest? value)))
(def (text-list? values)
(and (list? values) (every assurance-text? values)))
+(def (symbol-list? values)
+ (and (list? values) (every symbol? values)))
+(def (evidence-kind-list? values)
+ (and (list? values) (pair? values)
+ (every assurance-evidence-kind? values)))
+(def (verifier-choice-reason? value)
+ (memq value '(chosen request-blocked evidence-kind-unsupported
+ capability-mismatch lower-priority)))
+(def (composition-blocker? value)
+ (or (not value)
+ (memq value '(conflicted-snapshot unresolved-frontier
+ invalid-composition-claim insufficient-components
+ invalid-component-claim missing-direct-obligation
+ stale-direct-obligation))))
+(def (verification-blocker? value)
+ (or (not value)
+ (memq value '(conflicted-snapshot unresolved-frontier
+ capability-unavailable stale-obligation
+ dependency-unplanned dependency-blocked
+ dependency-cycle))))
+(def (replacement-blocker? value)
+ (or (not value)
+ (memq value '(source-conflicted source-unresolved change-unresolved
+ stale-source
+ target-unchanged target-conflicted target-unresolved
+ target-identity-changed graph-changed
+ policy-changed claim-missing claim-changed))))
+(def (replacement-declaration-blocker? value)
+ (or (not value)
+ (memq value '(derivation-mismatch source-requirement-missing
+ source-requirement-blocked target-frontier
+ candidate-missing candidate-not-fresh candidate-not-unbound
+ candidate-not-unknown candidate-claim-mismatch
+ candidate-subject-mismatch candidate-scope-mismatch
+ claim-not-declared support-link-missing
+ support-link-hypothetical))))
(def (revision-bindings? values)
(and (list? values)
(every (lambda (binding)
@@ -95,6 +162,15 @@
values)))
(def (witness-list? values)
(and (list? values) (every text-list? values)))
+(def (invalidation-trajectories? values)
+ (and (list? values)
+ (every (lambda (entry)
+ (and (list? entry)
+ (= (length entry) 3)
+ (assurance-text? (car entry))
+ (text-list? (cadr entry))
+ (text-list? (caddr entry))))
+ values)))
(def (nonnegative-integer? value)
(and (integer? value) (>= value 0)))
(def (one-of values)
@@ -151,7 +227,10 @@
(semantic-node-class
'aitia/obligation
(list (text-slot 'subject) (text-slot 'claim) (text-slot 'snapshot)
- (enum-slot 'evidence-kind symbol?) (enum-slot 'capability symbol?)
+ (enum-slot 'snapshot-revision maybe-text?)
+ (enum-slot 'snapshot-context-digest maybe-digest?)
+ (enum-slot 'evidence-kind assurance-evidence-kind?)
+ (enum-slot 'capability symbol?)
(text-slot 'scope))))
(def AssuranceEvidence
(semantic-node-class
@@ -213,12 +292,288 @@
(text-slot 'policy-revision)
(enum-slot 'evidence-identities text-list?)
(enum-slot 'state assurance-state?)
+ (enum-slot 'context-digest assurance-digest?)
(enum-slot 'digest assurance-digest?)
(enum-slot 'nodes node-list?)
(enum-slot 'relations relation-list?)
(enum-slot 'unresolved text-list?)
(enum-slot 'conflicts text-list?))))
+;;; A derivation names questions requiring a fresh declaration; it does not
+;;; construct, bind, discharge or authorize a replacement obligation.
+(def AssuranceReplacementRequirement
+ (poo-clos-class 'aitia/replacement-requirement
+ direct-slots:
+ (list (text-slot 'source-obligation)
+ (text-slot 'source-revision)
+ (text-slot 'claim)
+ (text-slot 'subject)
+ (enum-slot 'evidence-kind assurance-evidence-kind?)
+ (enum-slot 'capability symbol?)
+ (text-slot 'scope)
+ (enum-slot 'blocker replacement-blocker?))))
+(def (replacement-requirement-list? values)
+ (and (list? values) (every assurance-replacement-requirement? values)))
+(def AssuranceReplacementDerivation
+ (poo-clos-class 'aitia/replacement-derivation
+ direct-slots:
+ (list (enum-slot 'schema
+ (lambda (value)
+ (equal? value "lambda-aitia.replacement-requirements")))
+ (text-slot 'identity)
+ (enum-slot 'source-snapshot-digest assurance-digest?)
+ (enum-slot 'target-snapshot-digest assurance-digest?)
+ (enum-slot 'changed text-list?)
+ (enum-slot 'witnesses invalidation-trajectories?)
+ (enum-slot 'unresolved text-list?)
+ (enum-slot 'requirements replacement-requirement-list?)
+ (enum-slot 'digest assurance-digest?)
+ (enum-slot 'verifier-executed? (one-of '(#f)))
+ (enum-slot 'release-authorized? (one-of '(#f))))))
+
+;;; A caller-authored target obligation is checked and bound, never invented.
+;;; This receipt is inert even when its blocker is false.
+(def AssuranceReplacementDeclarationReceipt
+ (poo-clos-class 'aitia/replacement-declaration-receipt
+ direct-slots:
+ (list (enum-slot 'schema
+ (lambda (value)
+ (equal? value "lambda-aitia.replacement-declaration")))
+ (text-slot 'identity)
+ (enum-slot 'derivation-digest assurance-digest?)
+ (enum-slot 'source-snapshot-digest assurance-digest?)
+ (enum-slot 'draft-snapshot-digest assurance-digest?)
+ (enum-slot 'final-snapshot-digest maybe-digest?)
+ (text-slot 'source-obligation)
+ (text-slot 'target-obligation)
+ (enum-slot 'blocker replacement-declaration-blocker?)
+ (enum-slot 'digest assurance-digest?)
+ (enum-slot 'verifier-executed? (one-of '(#f)))
+ (enum-slot 'release-authorized? (one-of '(#f))))))
+
+;;; Public planning values remain POO-native and contain no executable hook.
+(def AssuranceVerificationPolicy
+ (poo-clos-class 'aitia/verification-policy
+ direct-slots:
+ (list (text-slot 'identity)
+ (text-slot 'revision)
+ (enum-slot 'capabilities symbol-list?)
+ (enum-slot 'planner-executes? (one-of '(#f)))
+ (enum-slot 'grants-authority? (one-of '(#f))))))
+(def AssuranceVerificationRequest
+ (poo-clos-class 'aitia/verification-request
+ direct-slots:
+ (list (text-slot 'obligation-identity)
+ (text-slot 'subject)
+ (text-slot 'claim)
+ (enum-slot 'snapshot-digest assurance-digest?)
+ (enum-slot 'evidence-kind assurance-evidence-kind?)
+ (enum-slot 'capability symbol?)
+ (enum-slot 'dependencies text-list?)
+ (enum-slot 'selected? boolean?)
+ (enum-slot 'blocker verification-blocker?))))
+(def (verification-request-list? values)
+ (and (list? values)
+ (every (lambda (value)
+ (assurance-verification-request? value))
+ values)))
+(def AssuranceVerificationPlan
+ (poo-clos-class 'aitia/verification-plan
+ direct-slots:
+ (list (enum-slot 'schema
+ (lambda (value)
+ (equal? value "lambda-aitia.planning-result")))
+ (text-slot 'identity)
+ (enum-slot 'snapshot-digest assurance-digest?)
+ (text-slot 'policy-identity)
+ (text-slot 'policy-revision)
+ (enum-slot 'digest assurance-digest?)
+ (enum-slot 'selected-obligations text-list?)
+ (enum-slot 'blocked-obligations text-list?)
+ (enum-slot 'changed text-list?)
+ (enum-slot 'impacted text-list?)
+ (enum-slot 'blocked-effects text-list?)
+ (enum-slot 'cycle-path text-list?)
+ (enum-slot 'cyclic-components witness-list?)
+ (enum-slot 'witnesses invalidation-trajectories?)
+ (enum-slot 'requests verification-request-list?)
+ (enum-slot 'unresolved text-list?)
+ (enum-slot 'conflicts text-list?)
+ (enum-slot 'verifier-executed? (one-of '(#f)))
+ (enum-slot 'release-authorized? (one-of '(#f)))
+ (enum-slot 'runtime-executed? (one-of '(#f))))))
+
+;;; These are declared candidates and inert explanation values, never adapter
+;;; handles or execution receipts.
+(def AssuranceVerifierCandidate
+ (poo-clos-class 'aitia/verifier-candidate
+ direct-slots:
+ (list (text-slot 'identity)
+ (text-slot 'revision)
+ (enum-slot 'priority nonnegative-integer?)
+ (enum-slot 'evidence-kinds evidence-kind-list?)
+ (enum-slot 'capability symbol?))))
+(def (verifier-candidate-list? values)
+ (and (list? values) (every assurance-verifier-candidate? values)))
+(def AssuranceVerifierChoice
+ (poo-clos-class 'aitia/verifier-choice
+ direct-slots:
+ (list (text-slot 'obligation-identity)
+ (text-slot 'candidate-identity)
+ (enum-slot 'selected? boolean?)
+ (enum-slot 'reason verifier-choice-reason?))))
+(def (verifier-choice-list? values)
+ (and (list? values) (every assurance-verifier-choice? values)))
+(def AssuranceVerifierChoiceReceipt
+ (poo-clos-class 'aitia/verifier-choice-receipt
+ direct-slots:
+ (list (enum-slot 'schema
+ (lambda (value)
+ (equal? value "lambda-aitia.verifier-choices")))
+ (text-slot 'identity)
+ (enum-slot 'plan-digest assurance-digest?)
+ (enum-slot 'candidates verifier-candidate-list?)
+ (enum-slot 'choices verifier-choice-list?)
+ (enum-slot 'unassigned-obligations text-list?)
+ (enum-slot 'digest assurance-digest?)
+ (enum-slot 'verifier-executed? (one-of '(#f)))
+ (enum-slot 'release-authorized? (one-of '(#f))))))
+
+;;; A reported tool result is not an admission. Input bindings carry both the
+;;; source revision and digest so a green exit cannot float across snapshots.
+(def AssuranceVerifierInput
+ (poo-clos-class 'aitia/verifier-input
+ direct-slots:
+ (list (text-slot 'identity) (text-slot 'revision)
+ (enum-slot 'digest assurance-digest?))))
+(def (verifier-input-list? values)
+ (and (list? values) (every assurance-verifier-input? values)))
+(def AssuranceVerifierOutcome
+ (poo-clos-class 'aitia/verifier-outcome
+ direct-slots:
+ (list (text-slot 'identity)
+ (enum-slot 'status
+ (one-of '(succeeded failed timed-out unavailable indeterminate)))
+ (text-slot 'producer) (text-slot 'tool) (text-slot 'tool-version)
+ (text-slot 'obligation) (text-slot 'subject) (text-slot 'scope)
+ (text-slot 'snapshot-revision)
+ (enum-slot 'snapshot-context-digest assurance-digest?)
+ (enum-slot 'inputs verifier-input-list?)
+ (enum-slot 'output-digest maybe-digest?)
+ (enum-slot 'accountable-authority maybe-text?)
+ (enum-slot 'review-scope maybe-text?))))
+(def AssuranceEvidenceAdmissionReceipt
+ (poo-clos-class 'aitia/evidence-admission-receipt
+ direct-slots:
+ (list (enum-slot 'schema
+ (lambda (value)
+ (equal? value "lambda-aitia.evidence-admission")))
+ (text-slot 'outcome) (text-slot 'obligation)
+ (enum-slot 'outcome-digest assurance-digest?)
+ (enum-slot 'snapshot-digest assurance-digest?)
+ (enum-slot 'digest assurance-digest?)
+ (enum-slot 'eligible? boolean?)
+ (enum-slot 'blocker
+ (lambda (value)
+ (or (not value)
+ (memq value
+ '(outcome-not-successful snapshot-conflicted
+ stale-obligation
+ outcome-binding-mismatch missing-output
+ missing-inputs duplicate-inputs
+ input-binding-mismatch
+ human-authority-missing)))))
+ (enum-slot 'verifier-executed? (one-of '(#f)))
+ (enum-slot 'release-authorized? (one-of '(#f))))))
+
+;;; Exact subject for a Host-owned POO Flow verification adapter. No
+;;; caller-authored presentation slot is an execution attestation by itself.
+(def AssuranceVerificationSubject
+ (poo-clos-class 'aitia/verification-subject
+ direct-slots:
+ (list (enum-slot 'snapshot (lambda (value) (assurance-snapshot? value)))
+ (enum-slot 'obligation (lambda (value) (assurance-obligation? value)))
+ (enum-slot 'evidence (lambda (value) (assurance-evidence? value)))
+ (enum-slot 'outcome
+ (lambda (value) (assurance-verifier-outcome? value))))))
+
+;;; Host-checked support inventory is an input to Decision evaluation, not a
+;;; Decision or an authority receipt. A POO presentation cannot grant release.
+(def (required-support-blocker? value)
+ (or (not value)
+ (memq value '(obligation-missing obligation-stale support-link-missing
+ admission-missing admission-invalid
+ evidence-link-missing))))
+(def AssessmentRequiredSupport
+ (poo-clos-class 'aitia/required-support-assessment
+ direct-slots:
+ (list (text-slot 'obligation)
+ (enum-slot 'supported? boolean?)
+ (enum-slot 'blocker required-support-blocker?))))
+(def (required-support-list? values)
+ (and (list? values) (every assessment-required-support? values)))
+(def (required-support-global-blocker? value)
+ (memq value '(snapshot-conflicted snapshot-unresolved claim-not-current
+ no-requirements duplicate-requirements
+ assumptions-unresolved defeaters-unresolved
+ claim-state-blocked challenge-unresolved source-changed)))
+(def (required-support-global-blockers? values)
+ (and (list? values)
+ (every required-support-global-blocker? values)))
+(def AssuranceRequiredSupport
+ (poo-clos-class 'aitia/required-support
+ direct-slots:
+ (list (enum-slot 'schema
+ (lambda (value)
+ (equal? value "lambda-aitia.required-support")))
+ (text-slot 'claim)
+ (enum-slot 'snapshot-digest assurance-digest?)
+ (enum-slot 'requirements required-support-list?)
+ (enum-slot 'blockers required-support-global-blockers?)
+ (enum-slot 'complete? boolean?)
+ (enum-slot 'release-authorized? (one-of '(#f))))))
+
+;;; A Host-issued preflight proves only that an undecided Decision and its
+;;; required support were current at issuance. Cedar/Runtime own authority.
+(def AssuranceDecisionPreflight
+ (poo-clos-class 'aitia/decision-preflight
+ direct-slots:
+ (list (enum-slot 'schema
+ (lambda (value)
+ (equal? value "lambda-aitia.decision-preflight")))
+ (text-slot 'identity)
+ (text-slot 'issuer)
+ (text-slot 'decision)
+ (text-slot 'claim)
+ (enum-slot 'snapshot-digest assurance-digest?)
+ (enum-slot 'release-authorized? (one-of '(#f))))))
+
+;;; A composition needs its own snapshot-bound obligation. Component support
+;;; remains separate and cannot discharge this structural requirement.
+(def AssuranceCompositionRequirement
+ (poo-clos-class 'aitia/composition-requirement
+ direct-slots:
+ (list (text-slot 'composition-claim)
+ (enum-slot 'component-claims text-list?)
+ (enum-slot 'direct-obligations text-list?)
+ (enum-slot 'current-obligations text-list?)
+ (enum-slot 'blocker composition-blocker?))))
+(def (composition-requirement-list? values)
+ (and (list? values)
+ (every assurance-composition-requirement? values)))
+(def AssuranceCompositionDerivation
+ (poo-clos-class 'aitia/composition-derivation
+ direct-slots:
+ (list (enum-slot 'schema
+ (lambda (value)
+ (equal? value "lambda-aitia.composition-requirements")))
+ (text-slot 'identity)
+ (enum-slot 'snapshot-digest assurance-digest?)
+ (enum-slot 'requirements composition-requirement-list?)
+ (enum-slot 'digest assurance-digest?)
+ (enum-slot 'verifier-executed? (one-of '(#f)))
+ (enum-slot 'release-authorized? (one-of '(#f))))))
+
(defstruct assurance-invalidation-receipt-record
(identity snapshot-digest changed impacted invalidated-evidence
required-obligations invalidated-decisions blocked-effects witnesses unresolved
@@ -227,22 +582,22 @@
transparent: #t)
(def (node-of-kind? class kind value)
- (and (poo-flow-model? class value) (eq? (.ref value 'kind) kind)))
+ (and (poo-clos-model? class value) (eq? (.ref value 'kind) kind)))
(def (assurance-node? value)
(and (object? value) (.slot? value 'kind)
(case (.ref value 'kind)
- ((artifact) (poo-flow-model? AssuranceArtifact value))
- ((claim) (poo-flow-model? AssuranceClaim value))
- ((assumption) (poo-flow-model? AssuranceAssumption value))
- ((observation) (poo-flow-model? AssuranceObservation value))
- ((event) (poo-flow-model? AssuranceEvent value))
- ((action) (poo-flow-model? AssuranceAction value))
- ((obligation) (poo-flow-model? AssuranceObligation value))
- ((evidence) (poo-flow-model? AssuranceEvidence value))
- ((counterexample) (poo-flow-model? AssuranceCounterexample value))
- ((finding) (poo-flow-model? AssuranceFinding value))
- ((decision) (poo-flow-model? AssuranceDecision value))
- ((effect) (poo-flow-model? AssuranceEffect value))
+ ((artifact) (poo-clos-model? AssuranceArtifact value))
+ ((claim) (poo-clos-model? AssuranceClaim value))
+ ((assumption) (poo-clos-model? AssuranceAssumption value))
+ ((observation) (poo-clos-model? AssuranceObservation value))
+ ((event) (poo-clos-model? AssuranceEvent value))
+ ((action) (poo-clos-model? AssuranceAction value))
+ ((obligation) (poo-clos-model? AssuranceObligation value))
+ ((evidence) (poo-clos-model? AssuranceEvidence value))
+ ((counterexample) (poo-clos-model? AssuranceCounterexample value))
+ ((finding) (poo-clos-model? AssuranceFinding value))
+ ((decision) (poo-clos-model? AssuranceDecision value))
+ ((effect) (poo-clos-model? AssuranceEffect value))
(else #f))))
(def (assurance-artifact? value) (node-of-kind? AssuranceArtifact 'artifact value))
(def (assurance-claim? value) (node-of-kind? AssuranceClaim 'claim value))
@@ -264,10 +619,69 @@
(node-of-kind? AssuranceDecision 'decision value))
(def (assurance-effect? value) (node-of-kind? AssuranceEffect 'effect value))
(def (assurance-relation? value)
- (and (poo-flow-model? AssuranceRelation value)
+ (and (poo-clos-model? AssuranceRelation value)
(eq? (.ref value 'plane)
(assurance-relation-kind-plane (.ref value 'relation)))))
-(def (assurance-snapshot? value) (poo-flow-model? AssuranceSnapshot value))
+(def (assurance-snapshot? value) (poo-clos-model? AssuranceSnapshot value))
+(def (assurance-replacement-requirement? value)
+ (poo-clos-model? AssuranceReplacementRequirement value))
+(def (assurance-replacement-derivation? value)
+ (poo-clos-model? AssuranceReplacementDerivation value))
+(def (assurance-replacement-declaration-receipt? value)
+ (and (poo-clos-model? AssuranceReplacementDeclarationReceipt value)
+ (eq? (not (.ref value 'blocker))
+ (if (.ref value 'final-snapshot-digest) #t #f))))
+(def (assurance-verification-policy? value)
+ (poo-clos-model? AssuranceVerificationPolicy value))
+(def (assurance-verification-request? value)
+ (and (poo-clos-model? AssuranceVerificationRequest value)
+ (eq? (.ref value 'selected?)
+ (not (.ref value 'blocker)))))
+(def (assurance-verification-plan? value)
+ (and (poo-clos-model? AssuranceVerificationPlan value)
+ (equal?
+ (.ref value 'selected-obligations)
+ (map (lambda (request) (.ref request 'obligation-identity))
+ (filter (lambda (request) (.ref request 'selected?))
+ (.ref value 'requests))))
+ (equal?
+ (.ref value 'blocked-obligations)
+ (map (lambda (request) (.ref request 'obligation-identity))
+ (filter (lambda (request) (not (.ref request 'selected?)))
+ (.ref value 'requests))))))
+(def (assurance-verifier-candidate? value)
+ (poo-clos-model? AssuranceVerifierCandidate value))
+(def (assurance-verifier-choice? value)
+ (and (poo-clos-model? AssuranceVerifierChoice value)
+ (eq? (.ref value 'selected?)
+ (eq? (.ref value 'reason) 'chosen))))
+(def (assurance-verifier-choice-receipt? value)
+ (poo-clos-model? AssuranceVerifierChoiceReceipt value))
+(def (assurance-verifier-input? value)
+ (poo-clos-model? AssuranceVerifierInput value))
+(def (assurance-verifier-outcome? value)
+ (poo-clos-model? AssuranceVerifierOutcome value))
+(def (assurance-evidence-admission-receipt? value)
+ (and (poo-clos-model? AssuranceEvidenceAdmissionReceipt value)
+ (eq? (.ref value 'eligible?) (not (.ref value 'blocker)))))
+(def (assessment-required-support? value)
+ (and (poo-clos-model? AssessmentRequiredSupport value)
+ (eq? (.ref value 'supported?) (not (.ref value 'blocker)))))
+(def (assurance-required-support? value)
+ (and (poo-clos-model? AssuranceRequiredSupport value)
+ (eq? (.ref value 'complete?)
+ (and (null? (.ref value 'blockers))
+ (pair? (.ref value 'requirements))
+ (every (lambda (item) (.ref item 'supported?))
+ (.ref value 'requirements))))))
+(def (assurance-decision-preflight? value)
+ (poo-clos-model? AssuranceDecisionPreflight value))
+(def (assurance-verification-subject? value)
+ (poo-clos-model? AssuranceVerificationSubject value))
+(def (assurance-composition-requirement? value)
+ (poo-clos-model? AssuranceCompositionRequirement value))
+(def (assurance-composition-derivation? value)
+ (poo-clos-model? AssuranceCompositionDerivation value))
(def (assurance-invalidation-receipt? value)
(assurance-invalidation-receipt-record? value))
(def (assurance-invalidation-receipt-ref receipt name)
diff --git a/modules/assurance/verification-projection.ss b/modules/assurance/verification-projection.ss
new file mode 100644
index 0000000..58237ba
--- /dev/null
+++ b/modules/assurance/verification-projection.ss
@@ -0,0 +1,73 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; Aitia owns the meaning of obligation dependencies; POO Flow owns Graph
+;;; ordering, reachability and cycle algorithms. No verifier runs here.
+
+(import (only-in :clan/poo/object .ref)
+ (only-in :gerbil/core list-sort)
+ :std/list/list
+ (only-in :poo-flow/src/graph/types
+ poo-flow-graph poo-flow-graph-edge poo-flow-graph-node)
+ :poo-flow/lambda-aitia/modules/assurance/types)
+
+(export assurance-verification-dependency-graph)
+
+(def (canonical-ids ids)
+ (list-sort string (delete-duplicates/hash ids)))
+
+(def (obligation-node? nodes identity)
+ (let ((node
+ (find (lambda (candidate)
+ (string=? (.ref candidate 'identity) identity))
+ nodes)))
+ (and node (assurance-obligation? node))))
+
+;;; Returns a POO Graph and the direct prerequisite IDs for each planned
+;;; obligation. A prerequisite outside the affected set remains visible to
+;;; the planner; it is never presumed discharged by an older plan.
+(def (assurance-verification-dependency-graph snapshot obligation-ids)
+ (unless (and (assurance-snapshot? snapshot)
+ (list? obligation-ids)
+ (every assurance-text? obligation-ids))
+ (error "invalid verification dependency projection"))
+ (let* ((ids (canonical-ids obligation-ids))
+ (nodes (.ref snapshot 'nodes))
+ (relations (.ref snapshot 'relations))
+ (prerequisites
+ (map
+ (lambda (id)
+ (cons id
+ (canonical-ids
+ (map (lambda (relation) (.ref relation 'target))
+ (filter
+ (lambda (relation)
+ (and (eq? (.ref relation 'plane) 'structural)
+ (eq? (.ref relation 'relation) 'depends-on)
+ (string=? (.ref relation 'source) id)
+ (obligation-node?
+ nodes (.ref relation 'target))))
+ relations)))))
+ ids))
+ (edges
+ (concatenate
+ (map
+ (lambda (entry)
+ (map (lambda (prerequisite)
+ (poo-flow-graph-edge prerequisite (car entry)
+ 'depends-on))
+ (filter (lambda (prerequisite)
+ (member prerequisite ids))
+ (cdr entry))))
+ prerequisites)))
+ (graph-value
+ (poo-flow-graph
+ (string-append (.ref snapshot 'graph-identity) "/verification")
+ (map poo-flow-graph-node ids)
+ edges
+ (list (cons 'semantic-owner 'lambda-aitia)
+ (cons 'graph-owner 'poo-flow)
+ (cons 'purpose 'verification-dependencies)))))
+ (values graph-value prerequisites)))
diff --git a/modules/formal-methods/config.ss b/modules/formal-methods/config.ss
index 038f90f..86aedde 100644
--- a/modules/formal-methods/config.ss
+++ b/modules/formal-methods/config.ss
@@ -8,20 +8,20 @@
;;; an engine ran or that a proof/model/policy was admitted.
(import (only-in :clan/poo/object .o)
- (only-in :poo-flow/src/modules/proof/interface
+ (only-in :poo-flow/modules/proof/interface
poo-flow-proof-module)
"types.ss"
"objects.ss"
"funs.ss")
-(export aitia-tla-plus-provider
+(export aitia-quint-provider
aitia-lean-provider
aitia-cedar-provider
aitia-formal-methods-module)
-(def aitia-tla-plus-provider
+(def aitia-quint-provider
(aitia-formal-method-provider
- 'tla+
+ 'quint
'state-transition-model-checking
'(module model-checking-configuration trace receipt)))
@@ -40,6 +40,6 @@
(def aitia-formal-methods-module
(poo-flow-proof-module
"lambda-aitia/modules/formal-methods"
- (.o (tla+ aitia-tla-plus-provider)
+ (.o (quint aitia-quint-provider)
(lean aitia-lean-provider)
(cedar aitia-cedar-provider))))
diff --git a/modules/gitops/config.ss b/modules/gitops/config.ss
index 20d4725..51ad858 100644
--- a/modules/gitops/config.ss
+++ b/modules/gitops/config.ss
@@ -3,7 +3,7 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
;;; -*- Gerbil -*-
-(import :poo-flow/src/module-system/declaration/interface
+(import :poo-flow/src/user-interface/module-selection
:poo-flow/lambda-aitia/modules/gitops/objects)
(export gitops-config)
(def (gitops-config selection)
diff --git a/modules/gitops/funs.ss b/modules/gitops/funs.ss
index c9afd81..550b3a0 100644
--- a/modules/gitops/funs.ss
+++ b/modules/gitops/funs.ss
@@ -10,7 +10,7 @@
(only-in :poo-flow/src/core/funcs
poo-flow-make-value-index poo-flow-value-index-put!
poo-flow-value-index-ref)
- :poo-flow/src/module-system/poo-clos/interface
+ :poo-flow/core/poo-clos/interface
:poo-flow/lambda-aitia/modules/gitops/types
:poo-flow/lambda-aitia/modules/gitops/objects)
(export GitOpsDefaultEvaluationMethod GitOpsDefaultEvaluationMethods
@@ -18,12 +18,23 @@
(def (composition-gitops-profiles composition)
(filter gitops-profile? (.ref composition 'profiles)))
-(def (composition-standard-names composition)
- (map (lambda (profile) (.ref profile 'name))
+(def (composition-standard-bindings composition)
+ (map (lambda (profile)
+ (let (standards (.ref profile 'standards))
+ (unless (and (list? standards) (= (length standards) 1))
+ (error "standard Profile must bind exactly one Standard"
+ (.ref profile 'name)))
+ (cons (.ref profile 'name) (car standards))))
(filter (lambda (profile)
(and (.slot? profile 'role)
(eq? (.ref profile 'role) 'standard)))
(.ref composition 'profiles))))
+(def (standard-check-current? check standard)
+ (and (.slot? standard 'edition)
+ (.slot? standard 'source-lock-digest)
+ (equal? (.ref check 'standard-edition) (.ref standard 'edition))
+ (equal? (.ref check 'source-lock-digest)
+ (.ref standard 'source-lock-digest))))
(def (check-index checks)
(let (index (poo-flow-make-value-index))
(for-each
@@ -35,17 +46,26 @@
(poo-flow-value-index-put! index (.ref check 'name) check)))
checks)
index))
-(def (gitops-profile-decision profile standards change checks)
+(def (gitops-profile-decision profile standard-bindings change checks)
(let* ((required (.ref profile 'required-checks))
+ (standards (map car standard-bindings))
(index (check-index checks))
- (missing '()) (failed '()) (stale '()))
+ (missing '()) (failed '()) (stale '())
+ (revision-mismatch? #f)
+ (standard-mismatch? #f))
(for-each
(lambda (name)
(let-values (((present? check) (poo-flow-value-index-ref index name)))
(cond ((not present?) (set! missing (cons name missing)))
((or (not (equal? (.ref check 'repository) (.ref change 'repository)))
(not (equal? (.ref check 'revision) (.ref change 'revision))))
- (set! stale (cons name stale)))
+ (set! stale (cons name stale))
+ (set! revision-mismatch? #t))
+ ((let (binding (assoc name standard-bindings))
+ (and binding
+ (not (standard-check-current? check (cdr binding)))))
+ (set! stale (cons name stale))
+ (set! standard-mismatch? #t))
((not (eq? (.ref check 'conclusion) 'success))
(set! failed (cons name failed))))))
(append required standards))
@@ -61,7 +81,10 @@
stale-checks: (reverse stale)
reasons: (append (if (null? missing) '() '(required-check-missing))
(if (null? failed) '() '(required-check-failed))
- (if (null? stale) '() '(check-revision-mismatch))))))))
+ (if revision-mismatch?
+ '(check-revision-mismatch) '())
+ (if standard-mismatch?
+ '(standard-assessment-mismatch) '())))))))
(def (gitops-unmatched-decision change)
(validate GitOpsDecision
(.new GitOpsDecision
@@ -80,7 +103,7 @@
(composition-gitops-profiles composition)))
(if profile
(gitops-profile-decision
- profile (composition-standard-names composition) change checks)
+ profile (composition-standard-bindings composition) change checks)
(gitops-unmatched-decision change))))
(def GitOpsDefaultEvaluationMethod
diff --git a/modules/gitops/objects.ss b/modules/gitops/objects.ss
index f46354d..50e3d42 100644
--- a/modules/gitops/objects.ss
+++ b/modules/gitops/objects.ss
@@ -7,13 +7,13 @@
;;; evaluator strategy axis where provider/runtime specializers may combine.
(import (only-in :clan/poo/object .def .o)
(only-in :clan/poo/mop .defgeneric .new)
- (only-in :poo-flow/src/module-system/poo-clos/interface
+ (only-in :poo-flow/core/poo-clos/interface
poo-clos-call
poo-clos-class
poo-clos-generic-function
poo-clos-generic-protocol
poo-clos-make-instance)
- (only-in :poo-flow/src/module-system/contribution/objects
+ (only-in :poo-flow/core/contribution/objects
make-contribution)
:poo-flow/lambda-aitia/modules/gitops/types)
(export GitOpsEvaluator GitOpsDefaultEvaluator
@@ -71,6 +71,10 @@
provider: provider-value event: event-value repository: repository-value
revision: revision-value source-ref: source-ref-value
target-ref: target-ref-value pull-request: pull-request-value))
-(def (gitops-check name-value repository-value revision-value conclusion-value)
+(def (gitops-check name-value repository-value revision-value conclusion-value
+ standard-edition: (standard-edition #f)
+ source-lock-digest: (source-lock-digest #f))
(.new GitOpsCheck name: name-value repository: repository-value
- revision: revision-value conclusion: conclusion-value))
+ revision: revision-value conclusion: conclusion-value
+ standard-edition: standard-edition
+ source-lock-digest: source-lock-digest))
diff --git a/modules/gitops/types.ss b/modules/gitops/types.ss
index cb5aa2c..27f2a5f 100644
--- a/modules/gitops/types.ss
+++ b/modules/gitops/types.ss
@@ -33,7 +33,9 @@
{name: {type: Symbol}
repository: {type: NonEmptyString}
revision: {type: NonEmptyString}
- conclusion: {type: GitOpsCheckConclusion}})
+ conclusion: {type: GitOpsCheckConclusion}
+ standard-edition: {type: (Or False NonEmptyString)}
+ source-lock-digest: {type: (Or False NonEmptyString)}})
(define-type (GitOpsDecision @ Class.)
slots: =>.+
{repository: {type: NonEmptyString}
diff --git a/modules/sdlc/README.org b/modules/sdlc/README.org
index 0dfd17f..67bce21 100644
--- a/modules/sdlc/README.org
+++ b/modules/sdlc/README.org
@@ -13,10 +13,81 @@ Projects may select multiple standards. No standard is automatically activated.
The activity vocabulary does not impose a waterfall sequence; projects own the
ordering, iteration and release process.
+* Design contracts and replacement review
+
+=design.ss= adds POO-native component design declarations under the SDLC owner.
+A contract names its ADR decision, responsibilities, owned state, allowed
+effects, assumptions, guarantees and variation points. Each clause has an
+identity, revision and statement; a variation declares which guarantees its
+change may affect. These are engineering declarations, not executable logical
+predicates or admitted evidence. Accepting the named ADR does not establish a
+guarantee.
+
+=sdlc-design-replacement-review= compares two revisions of the same contract.
+Its intentionally narrow declaration-level rule permits only exact clause
+identity/revision/statement matches: a candidate may require no new assumption
+and may discard no old guarantee. Added effects, changed state ownership,
+responsibility, ADR decision or variation declarations remain visible blockers.
+The receipt lists changed identities and guarantees requiring fresh checking;
+it never admits evidence or grants release authority. An unchanged declaration
+at a new revision still requires re-verification. Textual similarity is not
+treated as logical implication, and a =declared-substitutable?= candidate is
+not a proof of implementation refinement.
+
+Consumers may feed =changed-identities= into the existing
+=sdlc-change-impact= projection, which delegates trajectory analysis to POO
+Flow Graph. The job-executor regression does this for a retry-policy change:
+only the explicitly affected unique-effect guarantee and its verification
+are selected by the declared graph. If a variation omits a necessary
+dependency, this slice cannot discover it; model-to-implementation conformance
+and independent acceptance remain further qualification work.
+An alternative local-deduplication candidate changes state ownership,
+responsibility and the ADR decision. It is therefore blocked for design review
+even though its guarantee statements have the same text as the original.
+
+=implementation.ss= adds an inert design-to-source link. A link binds one
+guarantee revision to an Assurance artifact identity/revision/digest and the
+canonical Assurance snapshot context in which that source appears. The review
+requires every guarantee to have a current link and rejects changed contracts,
+guarantees, snapshots, source digests and unresolved/conflicted snapshot cuts.
+It reuses the Assurance snapshot owner rather than defining another source
+ledger. A current mapping means only that the declaration is consistent with
+the supplied snapshot. It does not validate the bytes behind an artifact,
+prove the implementation satisfies the guarantee, admit evidence or authorize
+release. Independent verifier/Host acceptance remains a separate gate.
+
+=verification.ss= projects a design guarantee through the existing Assurance
+Host. It requires a current implementation mapping, an exact current Claim
+whose predicate is the guarantee statement and whose assumptions match the
+design contract, a declared Claim-to-source edge,
+and a Host admission whose verifier input names that same source
+identity/revision/digest. The Host must also report the Claim's full required
+support set current. Missing source dependencies, unrelated inputs, copied or
+revoked admissions fail closed. The projection issues no admission and grants
+no release authority. Its =source-bound-support-current?= result is conditional
+on the application-configured Host; it does not establish verifier
+independence, semantic implementation conformance or trusted deployment.
+An application may configure the Assurance Host's =source-reader:= capability.
+With that reader, Host issuance and currentness check every declared verifier
+input against its Artifact SHA-256 using POO Flow's Source Lock algorithm.
+The configured verification operation then receives Host-materialized POO input
+values as its fourth argument, each with =identity=, =revision=, =digest= and a
+copy of the validated =payload=. The Host rechecks those values after the
+operation; replacing both payload and digest cannot retain a seal. Without a
+reader, the operation retains its three-argument contract.
+A byte mismatch or moving snapshot blocks issuance; observed byte drift
+invalidates previously issued receipts, which cannot revive merely because
+the bytes later return. =source-bytes-checked-support-current?= additionally
+requires the mapped design source to pass that check. Without the Host reader,
+the stronger result fails closed. SDLC review callers cannot supply replacement
+payloads. The application still owns the reader's repository/provenance trust
+and the verifier's actual process inputs; matching bytes alone proves neither
+Host independence nor semantic implementation conformance.
+
SDLC does not own CI providers or deployment environments. It contributes the
standard, project, obligation, evidence, traceability, tailoring and lifecycle
-models. Applications combine this module with POO Flow-owned workflow modules
-through =use-composition=.
+models. Applications combine these POO-native Profiles through POO Flow's
+=use-module= and =compose profiles=.
#+begin_export mermaid
flowchart LR
@@ -29,25 +100,20 @@ flowchart LR
Consumer -->|"host-owned execution"| Runtime[GitHub Actions or another CI host]
#+end_export
-The user-owned POO Flow =user-interface/init.ss= selects SDLC using the existing
-custom module syntax (paths are relative to that user directory):
+The maintained software-engineering Project selects base SDLC and NASA's
+standard-family contribution separately:
#+begin_src scheme
-(poo-flow!
- :custom
- (sdlc +nasa-7150-2d))
+(poo-flow-user-module-selection 'software-engineering 'sdlc '())
+(poo-flow-user-module-selection 'nasa 'sdlc '(+npr-7150.2))
#+end_src
-Here =sdlc= resolves through POO Flow's trusted contribution registry. A user
-can instead load a complete private collection with =(@ "./modules")=.
-
-Omit =+nasa-7150-2d= to select SDLC without NASA. =config.ss= owns the pure
-=sdlc-config= projection from a native module selection to a contribution.
-Unknown and duplicate flags fail explicitly. POO Flow's user-owned
-=user-interface/sdlc.ss= consumes the root init selections and exports
-=poo-flow-user-sdlc-contributions=. Consumers import that projection when needed;
-importing root init alone still only declares module selections. There is no
-automatic runtime activation. No example project is part of the SDLC build.
+Omit the NASA selection for standard-free SDLC. =config.ss= owns the pure
+=sdlc-config= projection; it rejects unsupported feature flags. The public
+profile identifies the standard family as =nasa/sdlc/npr-7150.2=. The selected
+edition =D= belongs to the Standard descriptor and source lock, so later
+editions need not change the family-level composition name. Composition does
+not activate a runtime.
=standards/nasa-7150-2d-profile.ss= owns the standard descriptor used by
=config.ss= and scenario Profiles. It contains the immutable source/catalog
diff --git a/modules/sdlc/config.ss b/modules/sdlc/config.ss
index 3d694ae..46d5b04 100644
--- a/modules/sdlc/config.ss
+++ b/modules/sdlc/config.ss
@@ -2,8 +2,9 @@
;;;
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
-;;; User selection projection. Importing this module enables no standard.
-(import :poo-flow/src/module-system/declaration/interface
+;;; Legacy module-selection projection is standard-free. The NASA Standard is
+;;; selected through its own typed nasa/sdlc Module Profile, not an SDLC flag.
+(import :poo-flow/src/user-interface/module-selection
:poo-flow/lambda-aitia/modules/sdlc/types
:poo-flow/lambda-aitia/modules/sdlc/objects
:poo-flow/lambda-aitia/modules/sdlc/funs
@@ -21,9 +22,6 @@
(equal? (poo-flow-user-module-selection-key selection) '(custom . sdlc)))
(error "expected a custom/sdlc selection"))
(let ((flags (poo-flow-user-module-selection-flags selection)))
- (unless (every (lambda (flag) (eq? flag '+nasa-7150-2d)) flags)
- (error "unsupported SDLC feature" flags))
- (unless (<= (length flags) 1)
- (error "duplicate SDLC feature" flags))
- (sdlc-contribution
- (if (null? flags) SdlcProfile sdlc-nasa-7150-profile))))
+ (unless (null? flags)
+ (error "SDLC has no feature flags; select standards as Modules" flags))
+ (sdlc-contribution SdlcProfile)))
diff --git a/modules/sdlc/design.ss b/modules/sdlc/design.ss
new file mode 100644
index 0000000..d318783
--- /dev/null
+++ b/modules/sdlc/design.ss
@@ -0,0 +1,239 @@
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; Aitia owns software-design meaning; POO Flow owns graph traversal and
+;;; runtime authority. This is an inert declaration-level replacement check.
+(import (only-in :clan/poo/object .o .ref)
+ (only-in :gerbil/core list-sort)
+ :std/list/list
+ :poo-flow/core/poo-clos/interface
+ (only-in :poo-flow/lambda-aitia/modules/assurance/funs
+ assurance-canonical-digest)
+ :poo-flow/lambda-aitia/modules/sdlc/types)
+
+(export SdlcDesignClause SdlcDesignVariation SdlcDesignContract
+ sdlc-design-clause? sdlc-design-variation? sdlc-design-contract?
+ sdlc-design-assumption sdlc-design-guarantee
+ sdlc-design-variation sdlc-design-contract
+ sdlc-design-contract-digest
+ sdlc-design-replacement-review)
+
+(def (text-list? values)
+ (and (list? values) (every sdlc-text? values)
+ (= (length values) (length (delete-duplicates/hash values)))))
+(def (nonempty-text-list? values)
+ (and (text-list? values) (pair? values)))
+(def (identified-list? values predicate)
+ (and (list? values) (every predicate values)
+ (let (ids (map (lambda (value) (.ref value 'identity)) values))
+ (= (length ids) (length (delete-duplicates/hash ids))))))
+(def (clause-list? kind)
+ (lambda (values)
+ (and (identified-list? values sdlc-design-clause?)
+ (every (lambda (value) (eq? (.ref value 'kind) kind)) values))))
+(def (variation-list? values)
+ (identified-list? values sdlc-design-variation?))
+(def (slot name predicate)
+ (poo-clos-direct-slot-definition name type-predicate: predicate))
+
+(def SdlcDesignClause
+ (poo-clos-class 'sdlc/design-clause
+ direct-slots:
+ (list (slot 'identity sdlc-text?) (slot 'revision sdlc-text?)
+ (slot 'kind (lambda (value) (if (memq value '(assumption guarantee)) #t #f)))
+ (slot 'statement sdlc-text?))))
+(def SdlcDesignVariation
+ (poo-clos-class 'sdlc/design-variation
+ direct-slots:
+ (list (slot 'identity sdlc-text?) (slot 'revision sdlc-text?)
+ (slot 'affects-guarantees nonempty-text-list?))))
+(def SdlcDesignContract
+ (poo-clos-class 'sdlc/design-contract
+ direct-slots:
+ (list (slot 'identity sdlc-text?) (slot 'revision sdlc-text?)
+ (slot 'decision sdlc-text?)
+ (slot 'responsibilities text-list?)
+ (slot 'owned-state text-list?)
+ (slot 'allowed-effects text-list?)
+ (slot 'assumptions (clause-list? 'assumption))
+ (slot 'guarantees (clause-list? 'guarantee))
+ (slot 'variations variation-list?))))
+
+(def (sdlc-design-clause? value)
+ (poo-clos-model? SdlcDesignClause value))
+(def (sdlc-design-variation? value)
+ (poo-clos-model? SdlcDesignVariation value))
+(def (sdlc-design-contract? value)
+ (and (poo-clos-model? SdlcDesignContract value)
+ (pair? (.ref value 'responsibilities))
+ (pair? (.ref value 'guarantees))
+ (let (guarantees (map (lambda (clause) (.ref clause 'identity))
+ (.ref value 'guarantees)))
+ (every (lambda (variation)
+ (every (lambda (id) (member id guarantees))
+ (.ref variation 'affects-guarantees)))
+ (.ref value 'variations)))))
+
+(def (design-clause kind-value id revision-value statement-value)
+ (poo-clos-check-model
+ SdlcDesignClause
+ (.o (:: @ (poo-clos-model-prototype SdlcDesignClause))
+ identity: id revision: revision-value kind: kind-value
+ statement: statement-value)))
+(def (sdlc-design-assumption id revision-value statement-value)
+ (design-clause 'assumption id revision-value statement-value))
+(def (sdlc-design-guarantee id revision-value statement-value)
+ (design-clause 'guarantee id revision-value statement-value))
+(def (sdlc-design-variation id revision-value affected-guarantees)
+ (poo-clos-check-model
+ SdlcDesignVariation
+ (.o (:: @ (poo-clos-model-prototype SdlcDesignVariation))
+ identity: id revision: revision-value
+ affects-guarantees: affected-guarantees)))
+(def (sdlc-design-contract id revision-value decision-value responsibility-values
+ owned-state-values effect-values assumption-values
+ guarantee-values variation-values)
+ (let (value
+ (poo-clos-check-model
+ SdlcDesignContract
+ (.o (:: @ (poo-clos-model-prototype SdlcDesignContract))
+ identity: id revision: revision-value decision: decision-value
+ responsibilities: responsibility-values
+ owned-state: owned-state-values
+ allowed-effects: effect-values assumptions: assumption-values
+ guarantees: guarantee-values variations: variation-values)))
+ (unless (sdlc-design-contract? value)
+ (error "design contract lacks a responsibility or guarantee, or a variation references an undeclared guarantee" id))
+ value))
+
+(def (ordered-ids values)
+ (list-sort string (delete-duplicates/hash values)))
+(def (clause-key value)
+ (list (.ref value 'identity) (.ref value 'revision)
+ (.ref value 'kind) (.ref value 'statement)))
+(def (variation-key value)
+ (list (.ref value 'identity) (.ref value 'revision)
+ (ordered-ids (.ref value 'affects-guarantees))))
+(def (ordered-keys values key)
+ (list-sort (lambda (left right) (string (car left) (car right)))
+ (map key values)))
+(def (contract-body value)
+ (list (.ref value 'decision)
+ (ordered-ids (.ref value 'responsibilities))
+ (ordered-ids (.ref value 'owned-state))
+ (ordered-ids (.ref value 'allowed-effects))
+ (ordered-keys (.ref value 'assumptions) clause-key)
+ (ordered-keys (.ref value 'guarantees) clause-key)
+ (ordered-keys (.ref value 'variations) variation-key)))
+(def (sdlc-design-contract-digest value)
+ (unless (sdlc-design-contract? value)
+ (error "invalid design contract for digest"))
+ (assurance-canonical-digest
+ (list 'lambda-aitia.sdlc-design-contract
+ (.ref value 'identity) (.ref value 'revision)
+ (contract-body value))))
+(def (changed-ids old-values new-values key)
+ (ordered-ids
+ (append
+ (map (lambda (value) (.ref value 'identity))
+ (filter (lambda (value) (not (member (key value) (map key new-values))))
+ old-values))
+ (map (lambda (value) (.ref value 'identity))
+ (filter (lambda (value) (not (member (key value) (map key old-values))))
+ new-values)))))
+(def (subset? left right)
+ (every (lambda (value) (member value right)) left))
+(def (clause-subset? left right)
+ (subset? (map clause-key left) (map clause-key right)))
+(def (variation-affected values ids)
+ (concatenate
+ (map (lambda (variation)
+ (if (member (.ref variation 'identity) ids)
+ (.ref variation 'affects-guarantees) '()))
+ values)))
+
+;;; Exact clause equality is the only declaration-level entailment admitted:
+;;; old assumptions must cover every new assumption, while new guarantees
+;;; must retain every old guarantee. No textual/logical equivalence is inferred.
+;;; Selected guarantees are re-verification requests, never admitted evidence.
+(def (sdlc-design-replacement-review old new)
+ (unless (and (sdlc-design-contract? old)
+ (sdlc-design-contract? new)
+ (equal? (.ref old 'identity) (.ref new 'identity)))
+ (error "design replacement requires two valid versions of one contract"))
+ (when (and (equal? (.ref old 'revision) (.ref new 'revision))
+ (not (equal? (contract-body old) (contract-body new))))
+ (error "design declaration changed without a revision change"))
+ (let* ((old-assumptions (.ref old 'assumptions))
+ (new-assumptions (.ref new 'assumptions))
+ (old-guarantees (.ref old 'guarantees))
+ (new-guarantees (.ref new 'guarantees))
+ (changed-assumptions
+ (changed-ids old-assumptions new-assumptions clause-key))
+ (changed-guarantees
+ (changed-ids old-guarantees new-guarantees clause-key))
+ (changed-variations
+ (changed-ids (.ref old 'variations) (.ref new 'variations)
+ variation-key))
+ (responsibilities-changed?
+ (not (equal? (ordered-ids (.ref old 'responsibilities))
+ (ordered-ids (.ref new 'responsibilities)))))
+ (state-changed?
+ (not (equal? (ordered-ids (.ref old 'owned-state))
+ (ordered-ids (.ref new 'owned-state)))))
+ (effects-changed?
+ (not (equal? (ordered-ids (.ref old 'allowed-effects))
+ (ordered-ids (.ref new 'allowed-effects)))))
+ (decision-changed?
+ (not (equal? (.ref old 'decision) (.ref new 'decision))))
+ (all-guarantees
+ (ordered-ids (append (map (lambda (v) (.ref v 'identity)) old-guarantees)
+ (map (lambda (v) (.ref v 'identity)) new-guarantees))))
+ (broad-change?
+ (or (pair? changed-assumptions) responsibilities-changed?
+ state-changed? effects-changed? decision-changed?))
+ (affected
+ (ordered-ids
+ (if broad-change?
+ all-guarantees
+ (append changed-guarantees
+ (variation-affected (.ref old 'variations)
+ changed-variations)
+ (variation-affected (.ref new 'variations)
+ changed-variations)))))
+ (blocker-values
+ (append
+ (if (clause-subset? new-assumptions old-assumptions) '()
+ '(stronger-or-changed-assumption))
+ (if (clause-subset? old-guarantees new-guarantees) '()
+ '(weaker-or-changed-guarantee))
+ (if (subset? (.ref new 'allowed-effects) (.ref old 'allowed-effects))
+ '() '(new-effect))
+ (if responsibilities-changed? '(responsibility-changed) '())
+ (if state-changed? '(state-ownership-changed) '())
+ (if decision-changed? '(decision-changed) '())
+ (if (pair? changed-variations) '(variation-changed) '()))))
+ (.o kind: 'sdlc.design-replacement-review
+ contract: (.ref old 'identity)
+ from-revision: (.ref old 'revision)
+ to-revision: (.ref new 'revision)
+ declared-substitutable?: (null? blocker-values)
+ blockers: blocker-values
+ changed-identities:
+ (ordered-ids
+ (append changed-assumptions changed-guarantees changed-variations
+ (if (or responsibilities-changed? state-changed? effects-changed?
+ decision-changed?
+ (and (not (equal? (.ref old 'revision)
+ (.ref new 'revision)))
+ (equal? (contract-body old)
+ (contract-body new))))
+ (list (.ref old 'identity)) '())))
+ recheck-guarantees:
+ (if (and (not (equal? (.ref old 'revision) (.ref new 'revision)))
+ (equal? (contract-body old) (contract-body new)))
+ all-guarantees affected)
+ evidence-admitted?: #f
+ release-authorized?: #f
+ runtime-executed?: #f)))
diff --git a/modules/sdlc/funs.ss b/modules/sdlc/funs.ss
index 25877a7..9230a4e 100644
--- a/modules/sdlc/funs.ss
+++ b/modules/sdlc/funs.ss
@@ -8,7 +8,7 @@
poo-flow-graph-topological-order)
(only-in :poo-flow/src/graph/types
poo-flow-graph poo-flow-graph-edge poo-flow-graph-node)
- (only-in :poo-flow/src/modules/governance/funs
+ (only-in :poo-flow/modules/governance/funs
poo-flow-governance-contribution)
:std/list/list)
(import :poo-flow/lambda-aitia/modules/sdlc/types :poo-flow/lambda-aitia/modules/sdlc/objects)
diff --git a/modules/sdlc/impact.ss b/modules/sdlc/impact.ss
index 1a0202a..bbc486e 100644
--- a/modules/sdlc/impact.ss
+++ b/modules/sdlc/impact.ss
@@ -9,7 +9,7 @@
:gerbil/core
(only-in :poo-flow/src/graph/types
poo-flow-graph poo-flow-graph-edge poo-flow-graph-node)
- (only-in :poo-flow/src/modules/temporal-causality/funs
+ (only-in :poo-flow/modules/temporal-causality/funs
poo-flow-structural-impact-analyze))
(export sdlc-change-impact)
(def (same-snapshot? value project)
diff --git a/modules/sdlc/implementation.ss b/modules/sdlc/implementation.ss
new file mode 100644
index 0000000..659fdd2
--- /dev/null
+++ b/modules/sdlc/implementation.ss
@@ -0,0 +1,141 @@
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; An inert, snapshot-bound design-to-source declaration. Source currentness
+;;; is checked against Assurance; semantic conformance is not inferred.
+(import (only-in :clan/poo/object .o .ref)
+ (only-in :gerbil/core list-sort)
+ :std/list/list
+ :poo-flow/core/poo-clos/interface
+ (only-in :poo-flow/lambda-aitia/modules/assurance/interface
+ assurance-artifact? assurance-digest?
+ assurance-snapshot? assurance-snapshot-canonical?)
+ :poo-flow/lambda-aitia/modules/sdlc/types
+ :poo-flow/lambda-aitia/modules/sdlc/design)
+
+(export SdlcDesignImplementationLink sdlc-design-implementation-link?
+ sdlc-design-implementation-link sdlc-design-implementation-review)
+
+(def (text-slot name)
+ (poo-clos-direct-slot-definition name type-predicate: sdlc-text?))
+(def (digest-slot name)
+ (poo-clos-direct-slot-definition name type-predicate: assurance-digest?))
+
+(def SdlcDesignImplementationLink
+ (poo-clos-class 'sdlc/design-implementation-link
+ direct-slots:
+ (list (text-slot 'contract) (text-slot 'contract-revision)
+ (digest-slot 'contract-digest)
+ (text-slot 'guarantee) (text-slot 'guarantee-revision)
+ (text-slot 'source) (text-slot 'source-revision)
+ (digest-slot 'source-digest)
+ (text-slot 'snapshot) (text-slot 'snapshot-revision)
+ (digest-slot 'snapshot-context-digest))))
+
+(def (sdlc-design-implementation-link? value)
+ (poo-clos-model? SdlcDesignImplementationLink value))
+
+(def (sdlc-design-implementation-link contract-value guarantee-value
+ source-value snapshot-value)
+ (unless (and (sdlc-design-contract? contract-value)
+ (sdlc-design-clause? guarantee-value)
+ (eq? (.ref guarantee-value 'kind) 'guarantee)
+ (assurance-artifact? source-value)
+ (assurance-snapshot-canonical? snapshot-value))
+ (error "invalid design implementation link input"))
+ (poo-clos-check-model
+ SdlcDesignImplementationLink
+ (.o (:: @ (poo-clos-model-prototype SdlcDesignImplementationLink))
+ contract: (.ref contract-value 'identity)
+ contract-revision: (.ref contract-value 'revision)
+ contract-digest: (sdlc-design-contract-digest contract-value)
+ guarantee: (.ref guarantee-value 'identity)
+ guarantee-revision: (.ref guarantee-value 'revision)
+ source: (.ref source-value 'identity)
+ source-revision: (.ref source-value 'revision)
+ source-digest: (.ref source-value 'content-digest)
+ snapshot: (.ref snapshot-value 'identity)
+ snapshot-revision: (.ref snapshot-value 'revision)
+ snapshot-context-digest: (.ref snapshot-value 'context-digest))))
+
+(def (ordered-ids values)
+ (list-sort string (delete-duplicates/hash values)))
+(def (find-id values id)
+ (find (lambda (value) (equal? (.ref value 'identity) id)) values))
+(def (bound-source snapshot id)
+ (let ((binding (assoc id (.ref snapshot 'source-revisions)))
+ (node (find-id (.ref snapshot 'nodes) id)))
+ (and binding node (assurance-artifact? node)
+ (equal? (cdr binding) (.ref node 'revision)) node)))
+
+(def (link-blocker contract snapshot link)
+ (let ((guarantee (find-id (.ref contract 'guarantees)
+ (.ref link 'guarantee)))
+ (source (bound-source snapshot (.ref link 'source))))
+ (cond
+ ((or (not (equal? (.ref link 'contract) (.ref contract 'identity)))
+ (not (equal? (.ref link 'contract-revision)
+ (.ref contract 'revision)))
+ (not (equal? (.ref link 'contract-digest)
+ (sdlc-design-contract-digest contract))))
+ 'contract-changed)
+ ((not guarantee) 'guarantee-undeclared)
+ ((not (equal? (.ref link 'guarantee-revision)
+ (.ref guarantee 'revision)))
+ 'guarantee-revision-changed)
+ ((or (not (equal? (.ref link 'snapshot) (.ref snapshot 'identity)))
+ (not (equal? (.ref link 'snapshot-revision)
+ (.ref snapshot 'revision)))
+ (not (equal? (.ref link 'snapshot-context-digest)
+ (.ref snapshot 'context-digest))))
+ 'snapshot-changed)
+ ((not source) 'source-unbound)
+ ((or (not (equal? (.ref link 'source-revision)
+ (.ref source 'revision)))
+ (not (equal? (.ref link 'source-digest)
+ (.ref source 'content-digest)))
+ (not (eq? (.ref source 'state) 'supported)))
+ 'source-changed)
+ (else #f))))
+
+(def (sdlc-design-implementation-review contract-value snapshot-value links)
+ (unless (and (sdlc-design-contract? contract-value)
+ (assurance-snapshot? snapshot-value)
+ (assurance-snapshot-canonical? snapshot-value)
+ (list? links)
+ (every sdlc-design-implementation-link? links))
+ (error "invalid design implementation review input"))
+ (let* ((checks
+ (map (lambda (link)
+ (.o guarantee: (.ref link 'guarantee)
+ source: (.ref link 'source)
+ blocker: (link-blocker contract-value snapshot-value link)))
+ links))
+ (current-guarantees
+ (ordered-ids
+ (map (lambda (check) (.ref check 'guarantee))
+ (filter (lambda (check) (not (.ref check 'blocker))) checks))))
+ (unmapped
+ (ordered-ids
+ (filter (lambda (id) (not (member id current-guarantees)))
+ (map (lambda (guarantee) (.ref guarantee 'identity))
+ (.ref contract-value 'guarantees)))))
+ (blocked (filter (lambda (check) (.ref check 'blocker)) checks))
+ (frontier? (or (pair? (.ref snapshot-value 'conflicts))
+ (pair? (.ref snapshot-value 'unresolved)))))
+ (.o kind: 'sdlc.design-implementation-review
+ contract: (.ref contract-value 'identity)
+ contract-revision: (.ref contract-value 'revision)
+ snapshot: (.ref snapshot-value 'identity)
+ snapshot-revision: (.ref snapshot-value 'revision)
+ snapshot-digest: (.ref snapshot-value 'digest)
+ mapping-current?: (and (null? blocked) (null? unmapped)
+ (not frontier?))
+ mapped-guarantees: current-guarantees
+ unmapped-guarantees: unmapped
+ blocked-links: blocked
+ snapshot-frontier?: frontier?
+ implementation-conforms?: #f
+ evidence-admitted?: #f
+ release-authorized?: #f)))
diff --git a/modules/sdlc/interface.ss b/modules/sdlc/interface.ss
index 3c99b44..edf8212 100644
--- a/modules/sdlc/interface.ss
+++ b/modules/sdlc/interface.ss
@@ -14,3 +14,9 @@
(export (import: :poo-flow/lambda-aitia/modules/sdlc/review))
(import :poo-flow/lambda-aitia/modules/sdlc/impact)
(export (import: :poo-flow/lambda-aitia/modules/sdlc/impact))
+(import :poo-flow/lambda-aitia/modules/sdlc/design)
+(export (import: :poo-flow/lambda-aitia/modules/sdlc/design))
+(import :poo-flow/lambda-aitia/modules/sdlc/implementation)
+(export (import: :poo-flow/lambda-aitia/modules/sdlc/implementation))
+(import :poo-flow/lambda-aitia/modules/sdlc/verification)
+(export (import: :poo-flow/lambda-aitia/modules/sdlc/verification))
diff --git a/modules/sdlc/objects.ss b/modules/sdlc/objects.ss
index 2869afc..149d65e 100644
--- a/modules/sdlc/objects.ss
+++ b/modules/sdlc/objects.ss
@@ -3,13 +3,13 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import (only-in :clan/poo/object .o .ref)
- (only-in :poo-flow/src/modules/governance/objects
+ (only-in :poo-flow/modules/governance/objects
PooFlowGovernanceProfile.)
- :poo-flow/src/module-system/contribution/model
+ :poo-flow/core/poo-clos/interface
:poo-flow/lambda-aitia/modules/sdlc/types)
(export StandardProfile. SdlcProfile)
(def StandardProfile.
- (.o (:: @ (poo-flow-model-prototype SdlcStandard)) kind: 'lambda-aitia.sdlc-standard coverage: 'partial
+ (.o (:: @ (poo-clos-model-prototype SdlcStandard)) kind: 'lambda-aitia.sdlc-standard coverage: 'partial
requirements: '() assessment: 'not-evaluated))
(def sdlc-ontology
(.o entities: '(LifecycleActivity Requirement Artifact Verification Evidence
@@ -33,17 +33,17 @@
(export sdlc-trace-node sdlc-trace-edge sdlc-trace-rule)
(def (sdlc-trace-rule id source-kind target-kind)
- (let ((value (.o (:: @ (poo-flow-model-prototype SdlcTraceRule)) identity: id source-category: source-kind target-category: target-kind)))
+ (let ((value (.o (:: @ (poo-clos-model-prototype SdlcTraceRule)) identity: id source-category: source-kind target-category: target-kind)))
(unless (sdlc-trace-rule? value) (error "invalid trace rule")) value))
(def (sdlc-trace-node id category-value project)
(unless (sdlc-bound-fact? project) (error "invalid trace project"))
- (let ((value (.o (:: @ (poo-flow-model-prototype SdlcTraceNode)) kind: 'sdlc.trace-node identity: id category: category-value
+ (let ((value (.o (:: @ (poo-clos-model-prototype SdlcTraceNode)) kind: 'sdlc.trace-node identity: id category: category-value
subject: (.ref project 'subject) revision: (.ref project 'revision)
scope: (.ref project 'scope))))
(unless (sdlc-trace-node? value) (error "invalid trace node")) value))
(def (sdlc-trace-edge id relation-value source-value target-value project)
(unless (sdlc-bound-fact? project) (error "invalid trace project"))
- (let ((value (.o (:: @ (poo-flow-model-prototype SdlcTraceEdge)) kind: 'sdlc.trace-edge identity: id relation: relation-value
+ (let ((value (.o (:: @ (poo-clos-model-prototype SdlcTraceEdge)) kind: 'sdlc.trace-edge identity: id relation: relation-value
source: source-value target: target-value
subject: (.ref project 'subject) revision: (.ref project 'revision)
scope: (.ref project 'scope))))
@@ -51,21 +51,21 @@
(export sdlc-project sdlc-obligation sdlc-evidence sdlc-tailoring-request)
(def (sdlc-project subject-value revision-value scope-value complete-value)
- (poo-flow-check-model SdlcProject
- (.o (:: @ (poo-flow-model-prototype SdlcProject)) kind: 'sdlc.project subject: subject-value revision: revision-value
+ (poo-clos-check-model SdlcProject
+ (.o (:: @ (poo-clos-model-prototype SdlcProject)) kind: 'sdlc.project subject: subject-value revision: revision-value
scope: scope-value complete?: complete-value)))
(def (sdlc-obligation standard-value requirement-value applicability-value)
- (poo-flow-check-model SdlcObligation
- (.o (:: @ (poo-flow-model-prototype SdlcObligation)) kind: 'sdlc.obligation standard: standard-value requirement: requirement-value
+ (poo-clos-check-model SdlcObligation
+ (.o (:: @ (poo-clos-model-prototype SdlcObligation)) kind: 'sdlc.obligation standard: standard-value requirement: requirement-value
applicability: applicability-value)))
(def (sdlc-evidence identity-value subject-value revision-value scope-value
standard-value requirement-value producer-value outcome-value)
- (poo-flow-check-model SdlcEvidence
- (.o (:: @ (poo-flow-model-prototype SdlcEvidence)) kind: 'sdlc.evidence identity: identity-value subject: subject-value
+ (poo-clos-check-model SdlcEvidence
+ (.o (:: @ (poo-clos-model-prototype SdlcEvidence)) kind: 'sdlc.evidence identity: identity-value subject: subject-value
revision: revision-value scope: scope-value standard: standard-value
requirement: requirement-value producer: producer-value outcome: outcome-value)))
(def (sdlc-tailoring-request subject-value revision-value standard-value
requirement-value rationale-value)
- (poo-flow-check-model SdlcTailoringRequest
- (.o (:: @ (poo-flow-model-prototype SdlcTailoringRequest)) kind: 'sdlc.tailoring-request subject: subject-value revision: revision-value
+ (poo-clos-check-model SdlcTailoringRequest
+ (.o (:: @ (poo-clos-model-prototype SdlcTailoringRequest)) kind: 'sdlc.tailoring-request subject: subject-value revision: revision-value
standard: standard-value requirement: requirement-value rationale: rationale-value)))
diff --git a/modules/sdlc/retirement-host.ss b/modules/sdlc/retirement-host.ss
new file mode 100644
index 0000000..b65a486
--- /dev/null
+++ b/modules/sdlc/retirement-host.ss
@@ -0,0 +1,244 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; Explicit, single-owner Scheme Host for the bounded retirement-ingress cut.
+;;; Pure POO state transitions are committed only at this local Host boundary.
+;;; This does not issue grants or claim durable, distributed, or Marlin authority.
+(import (only-in :clan/poo/object .o .ref .slot? object?))
+(export aitia-retirement-host
+ aitia-retirement-host-snapshot
+ aitia-retirement-effect-handoff
+ aitia-retirement-host-request-cancellation!
+ aitia-retirement-host-observe-completion!
+ aitia-retirement-host-observe-queue-drained!
+ aitia-retirement-host-complete-inventory!
+ aitia-retirement-host-review-cut!
+ aitia-retirement-host-observe-late-activity!
+ aitia-retirement-host-observe-inventory-gap!
+ aitia-retirement-host-close-ingress!
+ aitia-retirement-host-request-start!
+ aitia-retirement-host-attempt-start!
+ aitia-retirement-host-retire!)
+
+;;; The live state is keyed by host identity, never returned to callers.
+(def retirement-host-states (make-hash-table-eq weak-keys: #t))
+
+(def (host-state host)
+ (let (state (and (object? host) (hash-get retirement-host-states host)))
+ (unless (and state
+ (eq? (.ref state 'owner-thread) (current-thread)))
+ (error "retirement Host requires its serialized owner thread"))
+ state))
+
+(def (mutation-state host)
+ (let (current-state (host-state host))
+ (when (.ref current-state 'handoff-in-progress?)
+ (error "retirement Host mutation during effect handoff"))
+ current-state))
+
+(def (state-view state)
+ (.o kind: 'aitia.retirement-ingress-state.v1
+ active?: (.ref state 'active?)
+ queued?: (.ref state 'queued?)
+ inventory-known?: (.ref state 'inventory-known?)
+ reviewed?: (.ref state 'reviewed?)
+ cancellation-requested?: (.ref state 'cancellation-requested?)
+ late-activity-found?: (.ref state 'late-activity-found?)
+ inventory-gap-found?: (.ref state 'inventory-gap-found?)
+ retired?: (.ref state 'retired?)
+ ingress-open?: (.ref state 'ingress-open?)
+ authority-epoch: (.ref state 'authority-epoch)
+ cached-grant-epoch: (.ref state 'cached-grant-epoch)
+ pending?: (.ref state 'pending?)
+ attempted?: (.ref state 'attempted?)
+ handoff-in-progress?: (.ref state 'handoff-in-progress?)))
+
+(def (receipt action-name current-state was-accepted? rejection-reason did-handoff?)
+ (.o kind: 'aitia.retirement-ingress-receipt.v1
+ action: action-name accepted?: was-accepted? reason: rejection-reason
+ state: (state-view current-state)
+ handoff-executed?: did-handoff?
+ durable?: #f distributed-authority?: #f))
+
+(def (publish host action state accepted? reason)
+ (hash-put! retirement-host-states host state)
+ (receipt action state accepted? reason #f))
+
+(def (aitia-retirement-host)
+ (let ((host (.o kind: 'aitia.retirement-ingress-host.v1))
+ (creator-thread (current-thread)))
+ (let (state (.o owner-thread: creator-thread
+ active?: #t queued?: #t inventory-known?: #f
+ reviewed?: #f cancellation-requested?: #f
+ late-activity-found?: #f inventory-gap-found?: #f
+ retired?: #f ingress-open?: #t
+ authority-epoch: 0 cached-grant-epoch: 0
+ pending?: #f attempted?: #f
+ handoff-in-progress?: #f))
+ (hash-put! retirement-host-states host state)
+ host)))
+
+(def (aitia-retirement-host-snapshot host)
+ (state-view (host-state host)))
+
+;;; The named POO handoff is the only effect escape hatch. Its callback is
+;;; invoked only after the Host has consumed a current pending request.
+(def (aitia-retirement-effect-handoff callback)
+ (unless (procedure? callback) (error "retirement handoff requires a procedure"))
+ (.o kind: 'aitia.retirement-effect-handoff.v1 run: callback))
+
+(def (aitia-retirement-host-request-cancellation! host)
+ (let (state (mutation-state host))
+ (cond
+ ((.ref state 'retired?)
+ (receipt 'request-cancellation state #f 'already-retired #f))
+ ((.ref state 'cancellation-requested?)
+ (receipt 'request-cancellation state #f 'already-requested #f))
+ (else
+ (publish host 'request-cancellation
+ (.o (:: @ state) cancellation-requested?: #t)
+ #t 'recorded)))))
+
+(def (aitia-retirement-host-observe-completion! host)
+ (let (state (mutation-state host))
+ (cond
+ ((.ref state 'retired?)
+ (receipt 'observe-completion state #f 'already-retired #f))
+ ((not (.ref state 'active?))
+ (receipt 'observe-completion state #f 'not-active #f))
+ (else
+ (publish host 'observe-completion
+ (.o (:: @ state) active?: #f) #t 'observed)))))
+
+(def (aitia-retirement-host-observe-queue-drained! host)
+ (let (state (mutation-state host))
+ (cond
+ ((.ref state 'retired?)
+ (receipt 'observe-queue-drained state #f 'already-retired #f))
+ ((not (.ref state 'queued?))
+ (receipt 'observe-queue-drained state #f 'already-drained #f))
+ (else
+ (publish host 'observe-queue-drained
+ (.o (:: @ state) queued?: #f) #t 'observed)))))
+
+(def (aitia-retirement-host-complete-inventory! host)
+ (let (state (mutation-state host))
+ (cond
+ ((.ref state 'retired?)
+ (receipt 'complete-inventory state #f 'already-retired #f))
+ ((.ref state 'inventory-known?)
+ (receipt 'complete-inventory state #f 'already-known #f))
+ (else
+ (publish host 'complete-inventory
+ (.o (:: @ state) inventory-known?: #t) #t 'observed)))))
+
+(def (aitia-retirement-host-review-cut! host)
+ (let (state (mutation-state host))
+ (cond
+ ((.ref state 'retired?)
+ (receipt 'review-cut state #f 'already-retired #f))
+ ((.ref state 'active?)
+ (receipt 'review-cut state #f 'active-work #f))
+ ((.ref state 'queued?)
+ (receipt 'review-cut state #f 'queued-work #f))
+ ((not (.ref state 'inventory-known?))
+ (receipt 'review-cut state #f 'inventory-unknown #f))
+ ((.ref state 'reviewed?)
+ (receipt 'review-cut state #f 'already-reviewed #f))
+ (else
+ (publish host 'review-cut (.o (:: @ state) reviewed?: #t)
+ #t 'reviewed)))))
+
+(def (aitia-retirement-host-observe-late-activity! host)
+ (let (state (mutation-state host))
+ (cond
+ ((or (.ref state 'retired?) (not (.ref state 'reviewed?))
+ (.ref state 'late-activity-found?))
+ (receipt 'observe-late-activity state #f 'not-admissible #f))
+ (else
+ (publish host 'observe-late-activity
+ (.o (:: @ state) active?: #t late-activity-found?: #t)
+ #t 'observed)))))
+
+(def (aitia-retirement-host-observe-inventory-gap! host)
+ (let (state (mutation-state host))
+ (cond
+ ((or (.ref state 'retired?) (not (.ref state 'reviewed?))
+ (.ref state 'inventory-gap-found?))
+ (receipt 'observe-inventory-gap state #f 'not-admissible #f))
+ (else
+ (publish host 'observe-inventory-gap
+ (.o (:: @ state) inventory-known?: #f inventory-gap-found?: #t)
+ #t 'observed)))))
+
+;;; Closure and epoch advancement are one synchronous Host state publication.
+(def (aitia-retirement-host-close-ingress! host)
+ (let (state (mutation-state host))
+ (if (.ref state 'ingress-open?)
+ (publish host 'close-ingress
+ (.o (:: @ state) ingress-open?: #f
+ authority-epoch: (+ 1 (.ref state 'authority-epoch)))
+ #t 'fenced)
+ (receipt 'close-ingress state #f 'already-closed #f))))
+
+(def (aitia-retirement-host-request-start! host)
+ (let (state (host-state host))
+ (if (or (.ref state 'handoff-in-progress?)
+ (.ref state 'pending?) (.ref state 'attempted?))
+ (receipt 'request-start state #f 'request-already-consumed #f)
+ (publish host 'request-start (.o (:: @ state) pending?: #t)
+ #t 'pending))))
+
+(def (aitia-retirement-host-attempt-start! host handoff)
+ (let (state (mutation-state host))
+ (unless (and (object? handoff) (.slot? handoff 'kind)
+ (eq? (.ref handoff 'kind)
+ 'aitia.retirement-effect-handoff.v1)
+ (.slot? handoff 'run) (procedure? (.ref handoff 'run)))
+ (error "invalid retirement effect handoff"))
+ (unless (.ref state 'pending?)
+ (error "retirement start requires a pending request"))
+ (let (consumed (.o (:: @ state) pending?: #f attempted?: #t))
+ (cond
+ ((not (= (.ref state 'cached-grant-epoch)
+ (.ref state 'authority-epoch)))
+ (publish host 'attempt-start consumed #f 'stale-grant))
+ ((or (.ref state 'retired?) (not (.ref state 'ingress-open?)))
+ (publish host 'attempt-start consumed #f 'ingress-closed))
+ (else
+ ;; Consume before handoff so a reentrant call cannot start twice.
+ (let (started (.o (:: @ consumed) active?: #t
+ handoff-in-progress?: #t))
+ (hash-put! retirement-host-states host started)
+ ;; A failed callback is not completion. Keep work active and the
+ ;; request consumed, but release the transient reentrancy fence even
+ ;; when the callback raises. The original failure still propagates.
+ (dynamic-wind
+ void
+ (lambda () ((.ref handoff 'run)))
+ (lambda ()
+ (hash-put! retirement-host-states host
+ (.o (:: @ started) handoff-in-progress?: #f))))
+ (receipt 'attempt-start (host-state host) #t 'started #t)))))))
+
+(def (aitia-retirement-host-retire! host)
+ (let (state (mutation-state host))
+ (cond
+ ((.ref state 'retired?)
+ (receipt 'retire state #f 'already-retired #f))
+ ((.ref state 'active?)
+ (receipt 'retire state #f 'active-work #f))
+ ((.ref state 'queued?)
+ (receipt 'retire state #f 'queued-work #f))
+ ((not (.ref state 'inventory-known?))
+ (receipt 'retire state #f 'inventory-unknown #f))
+ ((not (.ref state 'reviewed?))
+ (receipt 'retire state #f 'review-missing #f))
+ ((.ref state 'ingress-open?)
+ (receipt 'retire state #f 'ingress-open #f))
+ ((= (.ref state 'cached-grant-epoch) (.ref state 'authority-epoch))
+ (receipt 'retire state #f 'grant-current #f))
+ (else
+ (publish host 'retire (.o (:: @ state) retired?: #t)
+ #t 'retired)))))
diff --git a/modules/sdlc/standards/nasa-7150-2d-catalog.ss b/modules/sdlc/standards/nasa-7150-2d-catalog.ss
index ef53579..8ef49a5 100644
--- a/modules/sdlc/standards/nasa-7150-2d-catalog.ss
+++ b/modules/sdlc/standards/nasa-7150-2d-catalog.ss
@@ -20,7 +20,7 @@
(page (list-ref row 2)) (excerpt (list-ref row 3))
(parts (list-ref row 4)) (notes (list-ref row 5))
(matrix (list-ref row 6))
- (value (.o identity: id standard: "nasa/npr-7150.2d"
+ (value (.o identity: id standard: "nasa/sdlc/npr-7150.2"
section: section-value source-url: (string-append
"https://nodis3.gsfc.nasa.gov/displayDir.cfm?Internal_ID=N_PR_7150_002D_&page_name=" page)
source-excerpt: excerpt source-parts: parts source-notes: notes
diff --git a/modules/sdlc/standards/nasa-7150-2d-profile.ss b/modules/sdlc/standards/nasa-7150-2d-profile.ss
index e98f903..6d2bbb0 100644
--- a/modules/sdlc/standards/nasa-7150-2d-profile.ss
+++ b/modules/sdlc/standards/nasa-7150-2d-profile.ss
@@ -7,16 +7,19 @@
;;; Applicability, review, and certification algorithms live in their own
;;; standard modules and do not enter the Profile import closure.
-(import (only-in :clan/poo/object .o)
+(import (only-in :clan/poo/object .o .ref)
:poo-flow/lambda-aitia/modules/sdlc/objects
- :poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d-catalog)
+ :poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d-catalog
+ (only-in :poo-flow/lambda-aitia/modules/sdlc/standards/sources
+ Nasa7150_2DSourcesLock))
(export Nasa7150_2D)
(def chapter3-url
"https://nodis3.gsfc.nasa.gov/displayDir.cfm?Internal_ID=N_PR_7150_002D_&page_name=Chapter3")
(def Nasa7150_2D
- (.o (:: @ StandardProfile.) identity: "nasa/npr-7150.2d" edition: "D"
+ (.o (:: @ StandardProfile.) identity: "nasa/sdlc/npr-7150.2" edition: "D"
+ source-lock-digest: (.ref Nasa7150_2DSourcesLock 'digest)
source-url: chapter3-url verified-on: "2026-09-12"
effective-date: "2022-03-08" expiration-date: "2027-03-08"
applicability-source:
diff --git a/modules/sdlc/standards/nasa-7150-2d.ss b/modules/sdlc/standards/nasa-7150-2d.ss
index f6f759f..8b467be 100644
--- a/modules/sdlc/standards/nasa-7150-2d.ss
+++ b/modules/sdlc/standards/nasa-7150-2d.ss
@@ -125,7 +125,7 @@
((eq? condition-value #t) 'applicable)
((eq? condition-value #f) 'condition-not-triggered)
(else 'context-review-required))))
- (.o kind: 'sdlc.applicability standard: "nasa/npr-7150.2d" requirement: identity-value
+ (.o kind: 'sdlc.applicability standard: "nasa/sdlc/npr-7150.2" requirement: identity-value
software-class: class-value status: status-value invocation: invocation-value
source-url: (.ref row 'source-url) section: (.ref row 'section)
tailoring: 'not-approved assessment: 'not-evaluated)))
diff --git a/modules/sdlc/standards/nasa-certification.ss b/modules/sdlc/standards/nasa-certification.ss
index 549e256..dba3a2e 100644
--- a/modules/sdlc/standards/nasa-certification.ss
+++ b/modules/sdlc/standards/nasa-certification.ss
@@ -5,8 +5,8 @@
;;; NPR 7150.2D compliance dossier and independently verified authority decision.
;;; This module never impersonates NASA, authenticates a signer, or authorizes release.
(import (only-in :clan/poo/object .o .ref .slot? object?)
- :poo-flow/src/module-system/contribution/model
- :poo-flow/src/module-system/contribution/verification
+ :poo-flow/core/poo-clos/interface
+ :poo-flow/core/contribution/verification
:poo-flow/lambda-aitia/modules/sdlc/types
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d-catalog
:std/list/list
@@ -51,9 +51,9 @@
'decision type-predicate:
(lambda (value) (if (memq value '(compliant noncompliant)) #t #f)))))))
(def (sdlc-compliance-dossier? value)
- (poo-flow-model? SdlcComplianceDossier value))
+ (poo-clos-model? SdlcComplianceDossier value))
(def (sdlc-certification-decision? value)
- (poo-flow-model? SdlcCertificationDecision value))
+ (poo-clos-model? SdlcCertificationDecision value))
(def (bound? value project)
(every (lambda (slot)
@@ -102,14 +102,14 @@
nasa-project-requirement-identities)
(eligible-gate? institution-gate-value 'institution
nasa-institution-requirement-identities))))
- (poo-flow-check-model
+ (poo-clos-check-model
SdlcComplianceDossier
- (.o (:: @ (poo-flow-model-prototype SdlcComplianceDossier))
+ (.o (:: @ (poo-clos-model-prototype SdlcComplianceDossier))
identity: identity-value
subject: (.ref project 'subject)
revision: (.ref project 'revision)
scope: (.ref project 'scope)
- standard: "nasa/npr-7150.2d"
+ standard: "nasa/sdlc/npr-7150.2"
edition: "NPR 7150.2D"
software-class: class-value
mapping-matrix-digest: matrix-digest
@@ -121,9 +121,9 @@
signer-value record-value decision-value)
(unless (sdlc-compliance-dossier? dossier-value)
(error "invalid NASA compliance dossier"))
- (poo-flow-check-model
+ (poo-clos-check-model
SdlcCertificationDecision
- (.o (:: @ (poo-flow-model-prototype SdlcCertificationDecision))
+ (.o (:: @ (poo-clos-model-prototype SdlcCertificationDecision))
identity: identity-value
subject: (.ref dossier-value 'subject)
revision: (.ref dossier-value 'revision)
@@ -141,9 +141,9 @@
(equal? (.ref decision-value 'dossier)
(.ref dossier-value 'identity)))
(error "certification decision is not bound to the dossier"))
- (poo-flow-check-model
+ (poo-clos-check-model
SdlcVerificationRequest
- (.o (:: @ (poo-flow-model-prototype SdlcVerificationRequest))
+ (.o (:: @ (poo-clos-model-prototype SdlcVerificationRequest))
subject: (.ref dossier-value 'subject)
revision: (.ref dossier-value 'revision)
scope: (.ref dossier-value 'scope)
diff --git a/modules/sdlc/standards/nasa-coverage.ss b/modules/sdlc/standards/nasa-coverage.ss
index d8dc6ce..2d176e5 100644
--- a/modules/sdlc/standards/nasa-coverage.ss
+++ b/modules/sdlc/standards/nasa-coverage.ss
@@ -59,7 +59,7 @@
(not (eq? (.ref row 'implementation-status)
'implemented)))
rows)))
- (.o standard: "nasa/npr-7150.2d"
+ (.o standard: "nasa/sdlc/npr-7150.2"
requirement-count: (length rows)
implemented-count:
(length (filter (lambda (row)
diff --git a/modules/sdlc/standards/nasa-lifecycle.ss b/modules/sdlc/standards/nasa-lifecycle.ss
index 11f5ff7..7454dbf 100644
--- a/modules/sdlc/standards/nasa-lifecycle.ss
+++ b/modules/sdlc/standards/nasa-lifecycle.ss
@@ -4,8 +4,8 @@
;;; Pure lifecycle decisions consume receipts from an explicit trusted boundary.
(import (only-in :clan/poo/object .o .ref .slot? object?)
- :poo-flow/src/module-system/contribution/model
- :poo-flow/src/module-system/contribution/verification
+ :poo-flow/core/poo-clos/interface
+ :poo-flow/core/contribution/verification
:poo-flow/lambda-aitia/modules/sdlc/types
(only-in :poo-flow/lambda-aitia/modules/sdlc/standards/nasa-policy
nasa-stage-policy
@@ -35,8 +35,8 @@
(error "verifiable NASA evidence requires a SHA-256 artifact digest"))
(.o (:: @ evidence) artifact-digest: digest-value))
(def (request project purpose-value facts-value)
- (poo-flow-check-model SdlcVerificationRequest
- (.o (:: @ (poo-flow-model-prototype SdlcVerificationRequest))
+ (poo-clos-check-model SdlcVerificationRequest
+ (.o (:: @ (poo-clos-model-prototype SdlcVerificationRequest))
subject: (.ref project 'subject) revision: (.ref project 'revision) scope: (.ref project 'scope)
purpose: purpose-value facts: facts-value)))
(def (nasa-context-request project class-value context-value)
@@ -179,8 +179,8 @@
reported-failures: failed
admitted?: (eq? status-value 'stage-admitted) release-authorized?: #f)))
(def (make-state project stage-value)
- (poo-flow-check-model SdlcLifecycleState
- (.o (:: @ (poo-flow-model-prototype SdlcLifecycleState))
+ (poo-clos-check-model SdlcLifecycleState
+ (.o (:: @ (poo-clos-model-prototype SdlcLifecycleState))
subject: (.ref project 'subject) revision: (.ref project 'revision) scope: (.ref project 'scope)
stage: stage-value)))
(def (nasa-lifecycle-start project)
diff --git a/modules/sdlc/standards/nasa-policy.ss b/modules/sdlc/standards/nasa-policy.ss
index 323dbae..91d561c 100644
--- a/modules/sdlc/standards/nasa-policy.ss
+++ b/modules/sdlc/standards/nasa-policy.ss
@@ -4,7 +4,7 @@
;;; NPR 7150.2D project/institution baseline policy construction.
(import (only-in :clan/poo/object .o .ref)
- :poo-flow/src/module-system/contribution/model
+ :poo-flow/core/poo-clos/interface
:poo-flow/lambda-aitia/modules/sdlc/types
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d-catalog
@@ -14,9 +14,9 @@
(def (nasa-stage-policy id requirements-value scope: (scope-value 'project))
(let (result
- (poo-flow-check-model
+ (poo-clos-check-model
SdlcStagePolicy
- (.o (:: @ (poo-flow-model-prototype SdlcStagePolicy))
+ (.o (:: @ (poo-clos-model-prototype SdlcStagePolicy))
identity: id assessment-scope: scope-value
requirements: requirements-value)))
(when (equal? id "unstarted")
diff --git a/modules/sdlc/standards/nasa-review.ss b/modules/sdlc/standards/nasa-review.ss
index 66eb3dd..73b41a4 100644
--- a/modules/sdlc/standards/nasa-review.ss
+++ b/modules/sdlc/standards/nasa-review.ss
@@ -4,7 +4,7 @@
;;; Source-bound review across every catalog requirement. No authority execution.
(import (only-in :clan/poo/object .o .ref .slot? object?)
- :poo-flow/src/module-system/contribution/model
+ :poo-flow/core/poo-clos/interface
:poo-flow/lambda-aitia/modules/sdlc/types
:poo-flow/lambda-aitia/modules/sdlc/objects
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d
@@ -27,11 +27,11 @@
(unless (sdlc-project? project) (error "invalid NASA evidence project"))
(unless (member criterion-id (map (lambda (c) (.ref c 'identity)) (nasa-requirement-criteria requirement-id)))
(error "unknown NASA criterion" requirement-id criterion-id))
- (poo-flow-check-model SdlcCriterionEvidence
- (.o (:: @ (poo-flow-model-prototype SdlcCriterionEvidence))
+ (poo-clos-check-model SdlcCriterionEvidence
+ (.o (:: @ (poo-clos-model-prototype SdlcCriterionEvidence))
identity: id kind: 'sdlc.evidence
subject: (.ref project 'subject) revision: (.ref project 'revision) scope: (.ref project 'scope)
- standard: "nasa/npr-7150.2d" requirement: requirement-id criterion: criterion-id
+ standard: "nasa/sdlc/npr-7150.2" requirement: requirement-id criterion: criterion-id
source-digest: (row-digest (nasa-requirement-by-id requirement-id))
producer: producer-value outcome: outcome-value)))
(def (unique-ids? values)
@@ -76,7 +76,7 @@
(error "invalid NASA project assessment inputs"))
(for-each
(lambda (e)
- (unless (and (equal? (.ref e 'standard) "nasa/npr-7150.2d")
+ (unless (and (equal? (.ref e 'standard) "nasa/sdlc/npr-7150.2")
(member (.ref e 'criterion)
(map (lambda (c) (.ref c 'identity))
(nasa-requirement-criteria (.ref e 'requirement)))))
@@ -117,8 +117,8 @@
(def (nasa-safety-component id project safety-value coverage-value complexity-value)
(unless (sdlc-project? project) (error "invalid safety snapshot"))
- (poo-flow-check-model SdlcSafetyComponent
- (.o (:: @ (poo-flow-model-prototype SdlcSafetyComponent)) identity: id
+ (poo-clos-check-model SdlcSafetyComponent
+ (.o (:: @ (poo-clos-model-prototype SdlcSafetyComponent)) identity: id
subject: (.ref project 'subject) revision: (.ref project 'revision) scope: (.ref project 'scope)
safety-critical?: safety-value mcdc-percent: coverage-value cyclomatic-complexity: complexity-value)))
(def (nasa-safety-review project components)
@@ -144,23 +144,23 @@
(def (nasa-tailoring-packet id project requirement-id rationale-value risk-value mitigations-value acceptance-value archive-value)
(unless (sdlc-project? project) (error "invalid tailoring project"))
(nasa-requirement-by-id requirement-id)
- (poo-flow-check-model SdlcTailoringPacket
- (.o (:: @ (poo-flow-model-prototype SdlcTailoringPacket)) identity: id
+ (poo-clos-check-model SdlcTailoringPacket
+ (.o (:: @ (poo-clos-model-prototype SdlcTailoringPacket)) identity: id
subject: (.ref project 'subject) revision: (.ref project 'revision) scope: (.ref project 'scope)
- standard: "nasa/npr-7150.2d" requirement: requirement-id
+ standard: "nasa/sdlc/npr-7150.2" requirement: requirement-id
rationale: rationale-value risk: risk-value mitigations: mitigations-value
risk-acceptance: acceptance-value archived-matrix: archive-value)))
(def (nasa-authority-attestation id packet authority-value signer-value record-value decision-value)
(unless (sdlc-tailoring-packet? packet) (error "invalid tailoring packet"))
- (poo-flow-check-model SdlcAuthorityAttestation
- (.o (:: @ (poo-flow-model-prototype SdlcAuthorityAttestation)) identity: id
+ (poo-clos-check-model SdlcAuthorityAttestation
+ (.o (:: @ (poo-clos-model-prototype SdlcAuthorityAttestation)) identity: id
subject: (.ref packet 'subject) revision: (.ref packet 'revision) scope: (.ref packet 'scope)
standard: (.ref packet 'standard) requirement: (.ref packet 'requirement)
request: (.ref packet 'identity) authority: authority-value signer: signer-value
record: record-value decision: decision-value)))
(def (nasa-tailoring-review project class-value context packet attestations)
(unless (and (sdlc-project? project) (sdlc-tailoring-packet? packet)
- (bound? packet project) (equal? (.ref packet 'standard) "nasa/npr-7150.2d")
+ (bound? packet project) (equal? (.ref packet 'standard) "nasa/sdlc/npr-7150.2")
(list? attestations) (every sdlc-authority-attestation? attestations)
(unique-ids? attestations))
(error "invalid tailoring review inputs"))
diff --git a/modules/sdlc/standards/nasa-standard-provider.ss b/modules/sdlc/standards/nasa-standard-provider.ss
index 05c5777..e643d99 100644
--- a/modules/sdlc/standards/nasa-standard-provider.ss
+++ b/modules/sdlc/standards/nasa-standard-provider.ss
@@ -12,7 +12,7 @@
(only-in :std/misc/ports read-all-as-string)
(only-in :poo-flow/src/feature-system/source-lock-feature
require-source-lock-payload)
- (only-in :poo-flow/src/modules/standards/interface
+ (only-in :poo-flow/modules/standards/interface
PooFlowStandardValidationProvider.
poo-flow-standard-artifact
poo-flow-standard-artifact-ref
@@ -44,11 +44,11 @@
nasa-7150-2d-validation-closure
nasa-7150-2d-materialize-sources)
-(def +nasa-7150-2d-provider-identity+ "lambda-aitia/nasa-npr-7150.2d")
+(def +nasa-7150-2d-provider-identity+ "lambda-aitia/nasa/sdlc/npr-7150.2/D")
(def Nasa7150_2DStandardFamily
(poo-flow-standard-family
- "nasa/npr-7150.2" "NASA" 'software-engineering
+ "nasa/sdlc/npr-7150.2" "NASA" 'sdlc
'(html scheme) "publicly-available-nasa-directive"
'((contributor . lambda-aitia)
(authority . nasa-nodis))))
diff --git a/modules/sdlc/standards/nasa-structured.ss b/modules/sdlc/standards/nasa-structured.ss
index 3c25c08..a9f698a 100644
--- a/modules/sdlc/standards/nasa-structured.ss
+++ b/modules/sdlc/standards/nasa-structured.ss
@@ -4,7 +4,7 @@
;;; Deterministic local checks over authenticated inventory projections.
(import (only-in :clan/poo/object .o .ref)
- :poo-flow/src/module-system/contribution/model
+ :poo-flow/core/poo-clos/interface
:poo-flow/lambda-aitia/modules/sdlc/types
:poo-flow/lambda-aitia/modules/sdlc/funs
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d
@@ -16,14 +16,14 @@
(every (lambda (s) (equal? (.ref value s) (.ref project s))) '(subject revision scope)))
(def (base class id project digest-value complete-value)
(unless (sdlc-project? project) (error "invalid inventory project"))
- (.o (:: @ (poo-flow-model-prototype class)) identity: id
+ (.o (:: @ (poo-clos-model-prototype class)) identity: id
subject: (.ref project 'subject) revision: (.ref project 'revision) scope: (.ref project 'scope)
artifact-digest: digest-value complete?: complete-value))
(def (nasa-safety-inventory id project digest-value components-value complete?: (complete-value #t))
- (poo-flow-check-model SdlcSafetyInventory
+ (poo-clos-check-model SdlcSafetyInventory
(.o (:: @ (base SdlcSafetyInventory id project digest-value complete-value)) components: components-value)))
(def (nasa-trace-inventory id project digest-value nodes-value edges-value complete?: (complete-value #t))
- (poo-flow-check-model SdlcTraceInventory
+ (poo-clos-check-model SdlcTraceInventory
(.o (:: @ (base SdlcTraceInventory id project digest-value complete-value)) nodes: nodes-value edges: edges-value)))
(def (nasa-structured-requirement? id)
(and (member id '("SWE-052" "SWE-219" "SWE-220")) #t))
diff --git a/modules/sdlc/standards/source.lock.ss b/modules/sdlc/standards/source.lock.ss
index a58732e..4c15d85 100644
--- a/modules/sdlc/standards/source.lock.ss
+++ b/modules/sdlc/standards/source.lock.ss
@@ -8,12 +8,12 @@
(def Nasa7150_2DSourcesLock
(sources-lock-value
- "lambda-aitia/nasa-npr-7150.2d/sources"
+ "lambda-aitia/nasa/sdlc/npr-7150.2/D/sources"
"2022-03-08"
- "sha256:286dbc723fbad0e596ad07593d95ce56f7bbdf6526d30719ab3bebd29bc772bb"
+ "sha256:cfb963b8cd81fd8e22e9b47251c1dc7927b85fcdf9ec9366057cd38cb94f338c"
(list
(.o (:: @ SourceLockEntry.)
- identity: "nasa/npr-7150.2d/appendix-c"
+ identity: "nasa/sdlc/npr-7150.2/D/appendix-c"
path: "modules/sdlc/standards/sources/appendix-c.html"
canonical-uri: "https://nodis3.gsfc.nasa.gov/displayDir.cfm?Internal_ID=N_PR_7150_002D_&page_name=AppendixC"
exact-version: "2022-03-08"
@@ -23,7 +23,7 @@
metadata: '((section . appendix-c))
)
(.o (:: @ SourceLockEntry.)
- identity: "nasa/npr-7150.2d/chapter2"
+ identity: "nasa/sdlc/npr-7150.2/D/chapter2"
path: "modules/sdlc/standards/sources/chapter2.html"
canonical-uri: "https://nodis3.gsfc.nasa.gov/displayDir.cfm?Internal_ID=N_PR_7150_002D_&page_name=Chapter2"
exact-version: "2022-03-08"
@@ -33,7 +33,7 @@
metadata: '((section . chapter-2))
)
(.o (:: @ SourceLockEntry.)
- identity: "nasa/npr-7150.2d/chapter3"
+ identity: "nasa/sdlc/npr-7150.2/D/chapter3"
path: "modules/sdlc/standards/sources/chapter3.html"
canonical-uri: "https://nodis3.gsfc.nasa.gov/displayDir.cfm?Internal_ID=N_PR_7150_002D_&page_name=Chapter3"
exact-version: "2022-03-08"
@@ -43,7 +43,7 @@
metadata: '((section . chapter-3))
)
(.o (:: @ SourceLockEntry.)
- identity: "nasa/npr-7150.2d/chapter4"
+ identity: "nasa/sdlc/npr-7150.2/D/chapter4"
path: "modules/sdlc/standards/sources/chapter4.html"
canonical-uri: "https://nodis3.gsfc.nasa.gov/displayDir.cfm?Internal_ID=N_PR_7150_002D_&page_name=Chapter4"
exact-version: "2022-03-08"
@@ -53,7 +53,7 @@
metadata: '((section . chapter-4))
)
(.o (:: @ SourceLockEntry.)
- identity: "nasa/npr-7150.2d/chapter5"
+ identity: "nasa/sdlc/npr-7150.2/D/chapter5"
path: "modules/sdlc/standards/sources/chapter5.html"
canonical-uri: "https://nodis3.gsfc.nasa.gov/displayDir.cfm?Internal_ID=N_PR_7150_002D_&page_name=Chapter5"
exact-version: "2022-03-08"
@@ -63,7 +63,7 @@
metadata: '((section . chapter-5))
)
(.o (:: @ SourceLockEntry.)
- identity: "nasa/npr-7150.2d/chapter6"
+ identity: "nasa/sdlc/npr-7150.2/D/chapter6"
path: "modules/sdlc/standards/sources/chapter6.html"
canonical-uri: "https://nodis3.gsfc.nasa.gov/displayDir.cfm?Internal_ID=N_PR_7150_002D_&page_name=Chapter6"
exact-version: "2022-03-08"
diff --git a/modules/sdlc/standards/sources.ss b/modules/sdlc/standards/sources.ss
index f5f3bef..1921359 100644
--- a/modules/sdlc/standards/sources.ss
+++ b/modules/sdlc/standards/sources.ss
@@ -17,7 +17,7 @@
Nasa7150_2DSources
Nasa7150_2DSourcesLock)
-(def +nasa-7150-2d-edition-identity+ "nasa/npr-7150.2d/2022-03-08")
+(def +nasa-7150-2d-edition-identity+ "nasa/sdlc/npr-7150.2/D/2022-03-08")
(def +nasa-7150-2d-edition-canonical-uri+
"https://nodis3.gsfc.nasa.gov/N_PR_7150_002D_")
(def +nasa-7150-2d-source-ref+
@@ -34,17 +34,17 @@
metadata: (list (cons 'section section-value))))
(def Nasa7150_2DSources
- (list (nasa-source "nasa/npr-7150.2d/chapter2"
+ (list (nasa-source "nasa/sdlc/npr-7150.2/D/chapter2"
"chapter2.html" "Chapter2" 'chapter-2)
- (nasa-source "nasa/npr-7150.2d/chapter3"
+ (nasa-source "nasa/sdlc/npr-7150.2/D/chapter3"
"chapter3.html" "Chapter3" 'chapter-3)
- (nasa-source "nasa/npr-7150.2d/chapter4"
+ (nasa-source "nasa/sdlc/npr-7150.2/D/chapter4"
"chapter4.html" "Chapter4" 'chapter-4)
- (nasa-source "nasa/npr-7150.2d/chapter5"
+ (nasa-source "nasa/sdlc/npr-7150.2/D/chapter5"
"chapter5.html" "Chapter5" 'chapter-5)
- (nasa-source "nasa/npr-7150.2d/chapter6"
+ (nasa-source "nasa/sdlc/npr-7150.2/D/chapter6"
"chapter6.html" "Chapter6" 'chapter-6)
- (nasa-source "nasa/npr-7150.2d/appendix-c"
+ (nasa-source "nasa/sdlc/npr-7150.2/D/appendix-c"
"appendix-c.html" "AppendixC" 'appendix-c)))
(include "source.lock.ss")
diff --git a/modules/sdlc/types.ss b/modules/sdlc/types.ss
index 9868fa9..2109d0c 100644
--- a/modules/sdlc/types.ss
+++ b/modules/sdlc/types.ss
@@ -3,8 +3,8 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import (only-in :clan/poo/object .ref .slot? object?)
- :poo-flow/src/module-system/contribution/model
- (only-in :poo-flow/src/modules/governance/types
+ :poo-flow/core/poo-clos/interface
+ (only-in :poo-flow/modules/governance/types
poo-flow-governance-profile?)
:std/list/list
:std/list/list)
@@ -73,22 +73,22 @@
(every standard-profile? (.ref value 'standards))
(let ((ids (map (lambda (s) (.ref s 'identity)) (.ref value 'standards))))
(= (length ids) (length (delete-duplicates/hash ids))))))
-(def (standard-profile? value) (poo-flow-model? SdlcStandard value))
-(def (sdlc-bound-fact? value) (poo-flow-model? SdlcBoundFact value))
-(def (sdlc-project? value) (poo-flow-model? SdlcProject value))
-(def (sdlc-obligation? value) (poo-flow-model? SdlcObligation value))
-(def (sdlc-evidence? value) (poo-flow-model? SdlcEvidence value))
-(def (sdlc-tailoring-request? value) (poo-flow-model? SdlcTailoringRequest value))
-(def (sdlc-trace-node? value) (poo-flow-model? SdlcTraceNode value))
-(def (sdlc-trace-edge? value) (poo-flow-model? SdlcTraceEdge value))
-(def (sdlc-trace-rule? value) (poo-flow-model? SdlcTraceRule value))
+(def (standard-profile? value) (poo-clos-model? SdlcStandard value))
+(def (sdlc-bound-fact? value) (poo-clos-model? SdlcBoundFact value))
+(def (sdlc-project? value) (poo-clos-model? SdlcProject value))
+(def (sdlc-obligation? value) (poo-clos-model? SdlcObligation value))
+(def (sdlc-evidence? value) (poo-clos-model? SdlcEvidence value))
+(def (sdlc-tailoring-request? value) (poo-clos-model? SdlcTailoringRequest value))
+(def (sdlc-trace-node? value) (poo-clos-model? SdlcTraceNode value))
+(def (sdlc-trace-edge? value) (poo-clos-model? SdlcTraceEdge value))
+(def (sdlc-trace-rule? value) (poo-clos-model? SdlcTraceRule value))
(export SdlcCriterionEvidence sdlc-criterion-evidence?
SdlcSafetyComponent sdlc-safety-component?)
(def SdlcCriterionEvidence
(poo-clos-class 'sdlc/criterion-evidence direct-superclasses: (list SdlcEvidence)
direct-slots: (text-slots '(criterion source-digest))))
-(def (sdlc-criterion-evidence? value) (poo-flow-model? SdlcCriterionEvidence value))
+(def (sdlc-criterion-evidence? value) (poo-clos-model? SdlcCriterionEvidence value))
(def (coverage-or-unknown? value)
(or (eq? value 'unknown) (and (real? value) (<= 0 value 100))))
(def (complexity-or-unknown? value)
@@ -99,7 +99,7 @@
(poo-clos-direct-slot-definition 'safety-critical? type-predicate: boolean?)
(poo-clos-direct-slot-definition 'mcdc-percent type-predicate: coverage-or-unknown?)
(poo-clos-direct-slot-definition 'cyclomatic-complexity type-predicate: complexity-or-unknown?))))
-(def (sdlc-safety-component? value) (poo-flow-model? SdlcSafetyComponent value))
+(def (sdlc-safety-component? value) (poo-clos-model? SdlcSafetyComponent value))
(export SdlcTailoringPacket sdlc-tailoring-packet?
SdlcAuthorityAttestation sdlc-authority-attestation?)
@@ -113,8 +113,8 @@
direct-slots: (append (text-slots '(request signer record))
(list (poo-clos-direct-slot-definition 'authority type-predicate: symbol?)
(enum-slot 'decision '(approved rejected))))))
-(def (sdlc-tailoring-packet? value) (poo-flow-model? SdlcTailoringPacket value))
-(def (sdlc-authority-attestation? value) (poo-flow-model? SdlcAuthorityAttestation value))
+(def (sdlc-tailoring-packet? value) (poo-clos-model? SdlcTailoringPacket value))
+(def (sdlc-authority-attestation? value) (poo-clos-model? SdlcAuthorityAttestation value))
(export SdlcStagePolicy sdlc-stage-policy? SdlcLifecycleState sdlc-lifecycle-state?
SdlcVerificationRequest sdlc-verification-request?)
@@ -132,9 +132,9 @@
(poo-clos-class 'sdlc/verification-request direct-superclasses: (list SdlcBoundFact)
direct-slots: (list (enum-slot 'purpose '(criterion-content classification-and-context tailoring-decision structured-inventory certification-decision))
(poo-clos-direct-slot-definition 'facts type-predicate: object?))))
-(def (sdlc-stage-policy? value) (poo-flow-model? SdlcStagePolicy value))
-(def (sdlc-lifecycle-state? value) (poo-flow-model? SdlcLifecycleState value))
-(def (sdlc-verification-request? value) (poo-flow-model? SdlcVerificationRequest value))
+(def (sdlc-stage-policy? value) (poo-clos-model? SdlcStagePolicy value))
+(def (sdlc-lifecycle-state? value) (poo-clos-model? SdlcLifecycleState value))
+(def (sdlc-verification-request? value) (poo-clos-model? SdlcVerificationRequest value))
(export SdlcSafetyInventory SdlcTraceInventory sdlc-safety-inventory? sdlc-trace-inventory?
sdlc-artifact-digest?)
@@ -154,5 +154,5 @@
(def SdlcTraceInventory
(poo-clos-class 'sdlc/trace-inventory direct-superclasses: (list SdlcInventory)
direct-slots: (list (instances-slot 'nodes sdlc-trace-node?) (instances-slot 'edges sdlc-trace-edge?))))
-(def (sdlc-safety-inventory? value) (poo-flow-model? SdlcSafetyInventory value))
-(def (sdlc-trace-inventory? value) (poo-flow-model? SdlcTraceInventory value))
+(def (sdlc-safety-inventory? value) (poo-clos-model? SdlcSafetyInventory value))
+(def (sdlc-trace-inventory? value) (poo-clos-model? SdlcTraceInventory value))
diff --git a/modules/sdlc/verification.ss b/modules/sdlc/verification.ss
new file mode 100644
index 0000000..68f0975
--- /dev/null
+++ b/modules/sdlc/verification.ss
@@ -0,0 +1,157 @@
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; Project a design guarantee onto existing Assurance Host admission.
+;;; This does not issue an admission, interpret a verifier, or authorize effects.
+(import (only-in :clan/poo/object .o .ref)
+ (only-in :gerbil/core list-sort)
+ :std/list/list
+ (only-in :poo-flow/lambda-aitia/modules/assurance/interface
+ assurance-claim? assurance-node-canonical
+ assurance-relation? assurance-snapshot-canonical?
+ assurance-verification-subject?
+ assurance-verification-subject-snapshot
+ assurance-host-admission-current?
+ assurance-host-source-current?
+ assurance-host-admitted-support?
+ assurance-host-required-support)
+ :poo-flow/lambda-aitia/modules/sdlc/design
+ :poo-flow/lambda-aitia/modules/sdlc/implementation)
+
+(export sdlc-design-guarantee-support-review)
+
+(def (ordered-ids values)
+ (list-sort string values))
+(def (node-at snapshot-value identity-value)
+ (find (lambda (node)
+ (equal? (.ref node 'identity) identity-value))
+ (.ref snapshot-value 'nodes)))
+(def (same-claim? snapshot-value claim-value)
+ (let (current (node-at snapshot-value (.ref claim-value 'identity)))
+ (and current (assurance-claim? current)
+ (equal? (assurance-node-canonical current)
+ (assurance-node-canonical claim-value))
+ (member (cons (.ref claim-value 'identity)
+ (.ref claim-value 'revision))
+ (.ref snapshot-value 'claim-revisions)))))
+(def (source-dependency? snapshot-value claim-value source-id)
+ (find (lambda (relation)
+ (and (eq? (.ref relation 'plane) 'structural)
+ (eq? (.ref relation 'relation) 'depends-on)
+ (equal? (.ref relation 'source) (.ref claim-value 'identity))
+ (equal? (.ref relation 'target) source-id)
+ (not (memq (.ref relation 'modality)
+ '(hypothesized counterfactual)))))
+ (.ref snapshot-value 'relations)))
+(def (input-bound? outcome-value link-value)
+ (find (lambda (input)
+ (and (equal? (.ref input 'identity) (.ref link-value 'source))
+ (equal? (.ref input 'revision)
+ (.ref link-value 'source-revision))
+ (equal? (.ref input 'digest)
+ (.ref link-value 'source-digest))))
+ (.ref outcome-value 'inputs)))
+
+;;; One Host admission must bind a verifier outcome to at least one source
+;;; declared for this guarantee. All claim obligations still need Host support.
+;;; The returned value is an inert assessment, never an authority token.
+(def (sdlc-design-guarantee-support-review
+ host-value contract-value guarantee-value implementation-links
+ claim-value subject-value relation-value admission-value admissions)
+ (unless (and (sdlc-design-contract? contract-value)
+ (sdlc-design-clause? guarantee-value)
+ (eq? (.ref guarantee-value 'kind) 'guarantee)
+ (list? implementation-links)
+ (every sdlc-design-implementation-link? implementation-links)
+ (assurance-claim? claim-value)
+ (assurance-verification-subject? subject-value)
+ (assurance-relation? relation-value)
+ (list? admissions))
+ (error "invalid design guarantee support review input"))
+ (let* ((snapshot-value (.ref subject-value 'snapshot))
+ (obligation-value (.ref subject-value 'obligation))
+ (outcome-value (.ref subject-value 'outcome))
+ (mapping
+ (sdlc-design-implementation-review
+ contract-value snapshot-value implementation-links))
+ (guarantee-current?
+ (find (lambda (declared)
+ (and (equal? (.ref declared 'identity)
+ (.ref guarantee-value 'identity))
+ (equal? (.ref declared 'revision)
+ (.ref guarantee-value 'revision))
+ (equal? (.ref declared 'statement)
+ (.ref guarantee-value 'statement))))
+ (.ref contract-value 'guarantees)))
+ (claim-current-value
+ (and (assurance-snapshot-canonical? snapshot-value)
+ (same-claim? snapshot-value claim-value)
+ (equal? (.ref claim-value 'subject)
+ (.ref contract-value 'identity))
+ (equal? (.ref claim-value 'predicate)
+ (.ref guarantee-value 'statement))
+ (equal? (ordered-ids (.ref claim-value 'assumptions))
+ (ordered-ids
+ (map (lambda (assumption)
+ (.ref assumption 'identity))
+ (.ref contract-value 'assumptions))))
+ (member (.ref obligation-value 'identity)
+ (.ref claim-value 'support-requirements))))
+ (source-links
+ (filter (lambda (link)
+ (equal? (.ref link 'guarantee)
+ (.ref guarantee-value 'identity)))
+ implementation-links))
+ (source-bound?
+ (find (lambda (link)
+ (and (source-dependency?
+ snapshot-value claim-value (.ref link 'source))
+ (input-bound? outcome-value link)))
+ source-links))
+ (source-bytes-bound?
+ (find (lambda (link)
+ (and (source-dependency?
+ snapshot-value claim-value (.ref link 'source))
+ (input-bound? outcome-value link)
+ (let (source-value
+ (node-at snapshot-value (.ref link 'source)))
+ (and source-value
+ (assurance-host-source-current?
+ host-value snapshot-value source-value)))))
+ source-links))
+ (admission-current?
+ (and admission-value
+ (memq admission-value admissions)
+ (assurance-host-admission-current? host-value admission-value)
+ (equal? (.ref admission-value 'subject-digest)
+ (assurance-verification-subject-snapshot subject-value))
+ (assurance-host-admitted-support?
+ host-value relation-value admission-value)))
+ (support
+ (and (.ref mapping 'mapping-current?) guarantee-current?
+ claim-current-value source-bound? admission-current?
+ (assurance-host-required-support
+ host-value claim-value admissions)))
+ (support-current?
+ (and support (.ref support 'complete?)
+ (equal? (.ref support 'snapshot-digest)
+ (.ref snapshot-value 'digest)))))
+ (.o kind: 'sdlc.design-guarantee-support-review
+ contract: (.ref contract-value 'identity)
+ contract-digest: (sdlc-design-contract-digest contract-value)
+ guarantee: (.ref guarantee-value 'identity)
+ snapshot-digest: (.ref snapshot-value 'digest)
+ mapping-current?: (.ref mapping 'mapping-current?)
+ claim-current?: (and guarantee-current? claim-current-value #t)
+ source-input-bound?: (and source-bound? #t)
+ source-bytes-match?: (and source-bytes-bound? #t)
+ host-admission-current?: (and admission-current? #t)
+ required-support-current?: (and support-current? #t)
+ source-bound-support-current?: (and support-current? #t)
+ source-bytes-checked-support-current?:
+ (and support-current? source-bytes-bound? #t)
+ verifier-independence-established?: #f
+ implementation-conforms?: #f
+ release-authorized?: #f
+ runtime-executed?: #f)))
diff --git a/proof/lean/AxiomAudit.lean b/proof/lean/AxiomAudit.lean
new file mode 100644
index 0000000..9d871e1
--- /dev/null
+++ b/proof/lean/AxiomAudit.lean
@@ -0,0 +1,29 @@
+-- SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+--
+-- SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+import LambdaAitiaProof.Modules.Assurance
+import LambdaAitiaProof.Modules.Sdlc
+
+open LambdaAitiaProof.Modules
+
+#print axioms Assurance.Invalidation.impactMonotoneForFixedSnapshot
+#print axioms Assurance.Invalidation.unresolvedFrontierBlocksClosure
+#print axioms Assurance.Invalidation.inertReceiptGrantsNoAuthority
+#print axioms Assurance.Composition.componentOnlyEvidenceCannotSupportComposite
+#print axioms Assurance.Composition.directObligationNeeded
+#print axioms Assurance.Composition.directWitnessDiffersFromComponentObligation
+#print axioms Sdlc.Design.refinesRefl
+#print axioms Sdlc.Design.refinesTrans
+#print axioms Sdlc.Design.strongerAssumptionBlocks
+#print axioms Sdlc.Design.droppedGuaranteeBlocks
+#print axioms Sdlc.Retirement.initialSafe
+#print axioms Sdlc.Retirement.stepPreservesSafety
+#print axioms Sdlc.Retirement.reachableSafe
+#print axioms Sdlc.Retirement.cancellationDoesNotJustifyRetirement
+#print axioms Sdlc.Retirement.oldReviewDoesNotJustifyRetirement
+#print axioms Sdlc.Retirement.unknownInventoryBlocksRetirement
+#print axioms Sdlc.RetirementIngress.initialSafe
+#print axioms Sdlc.RetirementIngress.currentGrantExcludesRetirement
+#print axioms Sdlc.RetirementIngress.stepPreservesSafety
+#print axioms Sdlc.RetirementIngress.reachableSafe
diff --git a/proof/lean/LambdaAitiaProof.lean b/proof/lean/LambdaAitiaProof.lean
index b1278d1..7e65ffd 100644
--- a/proof/lean/LambdaAitiaProof.lean
+++ b/proof/lean/LambdaAitiaProof.lean
@@ -3,3 +3,4 @@
-- SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
import LambdaAitiaProof.Modules.Assurance
+import LambdaAitiaProof.Modules.Sdlc
diff --git a/proof/lean/LambdaAitiaProof/Modules/Assurance.lean b/proof/lean/LambdaAitiaProof/Modules/Assurance.lean
index d155daa..8a0da03 100644
--- a/proof/lean/LambdaAitiaProof/Modules/Assurance.lean
+++ b/proof/lean/LambdaAitiaProof/Modules/Assurance.lean
@@ -3,3 +3,4 @@
-- SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
import LambdaAitiaProof.Modules.Assurance.Invalidation
+import LambdaAitiaProof.Modules.Assurance.Composition
diff --git a/proof/lean/LambdaAitiaProof/Modules/Assurance/Composition.lean b/proof/lean/LambdaAitiaProof/Modules/Assurance/Composition.lean
new file mode 100644
index 0000000..1450e2b
--- /dev/null
+++ b/proof/lean/LambdaAitiaProof/Modules/Assurance/Composition.lean
@@ -0,0 +1,79 @@
+-- SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+--
+-- SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+/-!
+The composition law is independent of the Graph traversal implementation.
+It states the admission boundary: evidence for components does not discharge
+the separate, current obligation declared for their composite claim.
+-/
+
+namespace LambdaAitiaProof.Modules.Assurance.Composition
+
+structure Contract (Claim Obligation : Type) where
+ componentOf : Claim → Claim → Prop
+ componentObligation : Obligation → Claim → Prop
+ directObligation : Obligation → Claim → Prop
+ current : Obligation → Prop
+ discharged : Obligation → Prop
+ compositeSupported : Claim → Prop
+ directEvidenceRequired :
+ ∀ composite, compositeSupported composite →
+ ∃ obligation, directObligation obligation composite ∧
+ current obligation ∧ discharged obligation
+ independent :
+ ∀ obligation component composite,
+ componentOf component composite →
+ componentObligation obligation component →
+ ¬ directObligation obligation composite
+
+def ComponentOnlyEvidence {Claim Obligation : Type}
+ (contract : Contract Claim Obligation) (composite : Claim) : Prop :=
+ ∀ obligation,
+ contract.current obligation ∧ contract.discharged obligation →
+ ∃ component,
+ contract.componentOf component composite ∧
+ contract.componentObligation obligation component
+
+theorem componentOnlyEvidenceCannotSupportComposite
+ {Claim Obligation : Type}
+ (contract : Contract Claim Obligation) (composite : Claim)
+ (onlyComponents : ComponentOnlyEvidence contract composite) :
+ ¬ contract.compositeSupported composite := by
+ intro supported
+ obtain ⟨obligation, direct, current, discharged⟩ :=
+ contract.directEvidenceRequired composite supported
+ obtain ⟨component, part, componentEvidence⟩ :=
+ onlyComponents obligation ⟨current, discharged⟩
+ exact (contract.independent obligation component composite part componentEvidence) direct
+
+theorem directObligationNeeded
+ {Claim Obligation : Type}
+ (contract : Contract Claim Obligation) (composite : Claim)
+ (supported : contract.compositeSupported composite) :
+ ∃ obligation,
+ contract.directObligation obligation composite ∧
+ contract.current obligation ∧ contract.discharged obligation :=
+ contract.directEvidenceRequired composite supported
+
+theorem directWitnessDiffersFromComponentObligation
+ {Claim Obligation : Type}
+ (contract : Contract Claim Obligation)
+ (component composite : Claim) (componentEvidence : Obligation)
+ (part : contract.componentOf component composite)
+ (belongsToComponent : contract.componentObligation componentEvidence component)
+ (supported : contract.compositeSupported composite) :
+ ∃ directEvidence,
+ contract.directObligation directEvidence composite ∧
+ contract.current directEvidence ∧
+ contract.discharged directEvidence ∧
+ directEvidence ≠ componentEvidence := by
+ obtain ⟨directEvidence, direct, current, discharged⟩ :=
+ contract.directEvidenceRequired composite supported
+ refine ⟨directEvidence, direct, current, discharged, ?_⟩
+ intro same
+ subst directEvidence
+ exact (contract.independent componentEvidence component composite
+ part belongsToComponent) direct
+
+end LambdaAitiaProof.Modules.Assurance.Composition
diff --git a/proof/lean/LambdaAitiaProof/Modules/Sdlc.lean b/proof/lean/LambdaAitiaProof/Modules/Sdlc.lean
new file mode 100644
index 0000000..18d1818
--- /dev/null
+++ b/proof/lean/LambdaAitiaProof/Modules/Sdlc.lean
@@ -0,0 +1,7 @@
+-- SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+--
+-- SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+import LambdaAitiaProof.Modules.Sdlc.Design
+import LambdaAitiaProof.Modules.Sdlc.Retirement
+import LambdaAitiaProof.Modules.Sdlc.RetirementIngress
diff --git a/proof/lean/LambdaAitiaProof/Modules/Sdlc/Design.lean b/proof/lean/LambdaAitiaProof/Modules/Sdlc/Design.lean
new file mode 100644
index 0000000..b43cffd
--- /dev/null
+++ b/proof/lean/LambdaAitiaProof/Modules/Sdlc/Design.lean
@@ -0,0 +1,53 @@
+-- SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+--
+-- SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+/-!
+A declaration-level substitution direction for software-design contracts.
+The predicates stand for exact, independently identified clauses. These laws
+do not prove that natural-language clauses hold in an implementation, that the
+Scheme comparison implements this relation, or that an effect is authorized.
+-/
+
+namespace LambdaAitiaProof.Modules.Sdlc.Design
+
+structure Contract (Clause : Type) where
+ assumptions : Clause → Prop
+ guarantees : Clause → Prop
+
+def Refines {Clause : Type} (old candidate : Contract Clause) : Prop :=
+ (∀ clause, candidate.assumptions clause → old.assumptions clause) ∧
+ (∀ clause, old.guarantees clause → candidate.guarantees clause)
+
+theorem refinesRefl {Clause : Type} (contract : Contract Clause) :
+ Refines contract contract := by
+ constructor <;> intro clause present <;> exact present
+
+theorem refinesTrans {Clause : Type}
+ (first second third : Contract Clause)
+ (firstToSecond : Refines first second)
+ (secondToThird : Refines second third) :
+ Refines first third := by
+ constructor
+ · intro clause required
+ exact firstToSecond.1 clause (secondToThird.1 clause required)
+ · intro clause promised
+ exact secondToThird.2 clause (firstToSecond.2 clause promised)
+
+theorem strongerAssumptionBlocks {Clause : Type}
+ (old candidate : Contract Clause) (clause : Clause)
+ (required : candidate.assumptions clause)
+ (notPreviouslyRequired : ¬ old.assumptions clause) :
+ ¬ Refines old candidate := by
+ intro replacement
+ exact notPreviouslyRequired (replacement.1 clause required)
+
+theorem droppedGuaranteeBlocks {Clause : Type}
+ (old candidate : Contract Clause) (clause : Clause)
+ (promised : old.guarantees clause)
+ (notPromised : ¬ candidate.guarantees clause) :
+ ¬ Refines old candidate := by
+ intro replacement
+ exact notPromised (replacement.2 clause promised)
+
+end LambdaAitiaProof.Modules.Sdlc.Design
diff --git a/proof/lean/LambdaAitiaProof/Modules/Sdlc/Retirement.lean b/proof/lean/LambdaAitiaProof/Modules/Sdlc/Retirement.lean
new file mode 100644
index 0000000..e01cee2
--- /dev/null
+++ b/proof/lean/LambdaAitiaProof/Modules/Sdlc/Retirement.lean
@@ -0,0 +1,81 @@
+-- SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+-- SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+/-!
+Design-level retirement safety. `Step.observe` overapproximates every
+pre-retirement observation, including late work and inventory loss; safety
+cannot rely on an old review. Terminal states admit no further observations.
+This is not a proof of Quint/Scheme correspondence, inventory truth, liveness,
+post-retirement ingress exclusion, persistence, or effect authorization.
+-/
+
+namespace LambdaAitiaProof.Modules.Sdlc.Retirement
+
+structure State where
+ active : Bool
+ queued : Bool
+ known : Bool
+ reviewed : Bool
+ cancelRequested : Bool
+ retired : Bool
+
+def Safe (s : State) : Prop :=
+ s.retired = true → s.active = false ∧ s.queued = false ∧ s.known = true
+
+def Initial : State := ⟨true, true, false, false, false, false⟩
+
+inductive Step : State → State → Prop where
+ | observe (s t : State) (before : s.retired = false)
+ (after : t.retired = false) : Step s t
+ | finalize (s : State) (before : s.retired = false)
+ (review : s.reviewed = true) (idle : s.active = false)
+ (drained : s.queued = false) (inventory : s.known = true) :
+ Step s { s with retired := true }
+ | terminal (s : State) (done : s.retired = true) : Step s s
+
+theorem initialSafe : Safe Initial := by
+ intro done
+ cases done
+
+theorem stepPreservesSafety {s t : State} (safe : Safe s)
+ (step : Step s t) : Safe t := by
+ cases step with
+ | observe t before after =>
+ intro done
+ have impossible : false = true := after.symm.trans done
+ cases impossible
+ | finalize before review idle drained inventory =>
+ intro _
+ exact ⟨idle, drained, inventory⟩
+ | terminal done => exact safe
+
+inductive Reachable : State → Prop where
+ | initial : Reachable Initial
+ | next {s t : State} : Reachable s → Step s t → Reachable t
+
+theorem reachableSafe {s : State} (reachable : Reachable s) : Safe s := by
+ induction reachable with
+ | initial => exact initialSafe
+ | next previous step inductionHypothesis =>
+ exact stepPreservesSafety inductionHypothesis step
+
+def CancellationOnly : State := ⟨true, true, true, false, true, true⟩
+def StaleReview : State := ⟨true, false, true, true, false, true⟩
+def UnknownInventory : State := ⟨false, false, false, true, false, true⟩
+
+theorem cancellationDoesNotJustifyRetirement : ¬ Safe CancellationOnly := by
+ intro safe
+ have impossible := (safe rfl).1
+ cases impossible
+
+theorem oldReviewDoesNotJustifyRetirement : ¬ Safe StaleReview := by
+ intro safe
+ have impossible := (safe rfl).1
+ cases impossible
+
+theorem unknownInventoryBlocksRetirement : ¬ Safe UnknownInventory := by
+ intro safe
+ have impossible := (safe rfl).2.2
+ cases impossible
+
+end LambdaAitiaProof.Modules.Sdlc.Retirement
diff --git a/proof/lean/LambdaAitiaProof/Modules/Sdlc/RetirementIngress.lean b/proof/lean/LambdaAitiaProof/Modules/Sdlc/RetirementIngress.lean
new file mode 100644
index 0000000..98a76fe
--- /dev/null
+++ b/proof/lean/LambdaAitiaProof/Modules/Sdlc/RetirementIngress.lean
@@ -0,0 +1,92 @@
+-- SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+-- SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+import LambdaAitiaProof.Modules.Sdlc.Retirement
+
+/-!
+An abstract extension of retirement review with an ingress fence. Requests
+and rejections may stutter after retirement; an effect with a current epoch
+cannot start there. Finalization requires a closed issuance boundary and an
+invalidated cached epoch. No proof of deployed revocation, Quint correspondence,
+grant issuance, fairness or persistent state is claimed.
+-/
+
+namespace LambdaAitiaProof.Modules.Sdlc.RetirementIngress
+
+structure State where
+ review : Retirement.State
+ ingressOpen : Bool
+ epoch : Nat
+ grantEpoch : Nat
+
+def Safe (s : State) : Prop :=
+ Retirement.Safe s.review ∧
+ (s.review.retired = true → s.ingressOpen = false ∧ s.grantEpoch ≠ s.epoch)
+
+def Initial : State := ⟨Retirement.Initial, true, 0, 0⟩
+
+theorem initialSafe : Safe Initial := by
+ constructor
+ · exact Retirement.initialSafe
+ · intro retired
+ cases retired
+
+theorem currentGrantExcludesRetirement {s : State} (safe : Safe s)
+ (current : s.grantEpoch = s.epoch) : s.review.retired ≠ true := by
+ intro retired
+ exact (safe.2 retired).2 current
+
+inductive Step : State → State → Prop where
+ | review (s : State) (t : Retirement.State)
+ (step : Retirement.Step s.review t) (beforeFinalization : t.retired = false) :
+ Step s {s with review := t}
+ | close (s : State) (notRetired : s.review.retired = false)
+ (wasOpen : s.ingressOpen = true) :
+ Step s {s with ingressOpen := false, epoch := s.epoch + 1}
+ | finalize (s : State)
+ (step : Retirement.Step s.review {s.review with retired := true})
+ (closed : s.ingressOpen = false) (revoked : s.grantEpoch ≠ s.epoch) :
+ Step s {s with review := {s.review with retired := true}}
+ | start (s : State) (current : s.grantEpoch = s.epoch) :
+ Step s {s with review := {s.review with active := true}}
+ | requestOrReject (s : State) : Step s s
+
+theorem stepPreservesSafety {s t : State} (safe : Safe s)
+ (step : Step s t) : Safe t := by
+ cases step with
+ | review t step beforeFinalization =>
+ constructor
+ · exact Retirement.stepPreservesSafety safe.1 step
+ · intro retired
+ have impossible : false = true := beforeFinalization.symm.trans retired
+ cases impossible
+ | close notRetired wasOpen =>
+ constructor
+ · exact safe.1
+ · intro retired
+ have impossible : false = true := notRetired.symm.trans retired
+ cases impossible
+ | finalize step closed revoked =>
+ constructor
+ · exact Retirement.stepPreservesSafety safe.1 step
+ · intro _
+ exact ⟨closed, revoked⟩
+ | start current =>
+ constructor
+ · intro retired
+ exact False.elim (currentGrantExcludesRetirement safe current retired)
+ · intro retired
+ exact False.elim (currentGrantExcludesRetirement safe current retired)
+ | requestOrReject => exact safe
+
+inductive Reachable : State → Prop where
+ | initial : Reachable Initial
+ | next {s t : State} : Reachable s → Step s t → Reachable t
+
+theorem reachableSafe {s : State} (reachable : Reachable s) : Safe s := by
+ induction reachable with
+ | initial => exact initialSafe
+ | next previous step inductionHypothesis =>
+ exact stepPreservesSafety inductionHypothesis step
+
+end LambdaAitiaProof.Modules.Sdlc.RetirementIngress
diff --git a/proof/lean/README.org b/proof/lean/README.org
index be6556f..5e0305c 100644
--- a/proof/lean/README.org
+++ b/proof/lean/README.org
@@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
-#+title: Lambda Aitia Assurance Proofs
+#+title: Lambda Aitia Assurance and SDLC Proofs
This is Lambda Aitia's source-owned Lean package. It states laws over Aitia
assurance vocabulary and does not import or copy POO Flow's generic proof
@@ -22,3 +22,23 @@ that impact is monotone when the changed set expands over one fixed
reachability relation, that a non-empty unresolved frontier prevents closure,
and that an inert invalidation receipt grants no release authority or runtime
execution.
+
+=LambdaAitiaProof.Modules.Assurance.Composition= proves a separate admission
+law: current evidence belonging only to component obligations cannot support
+the composite claim. Any admitted composite has a current discharged direct
+obligation distinct from each component obligation. These theorems assume the
+explicit =Contract= fields; the Scheme acceptance tests check that the
+runtime projection observes the same boundary, rather than treating a Lean
+theorem as runtime authority.
+
+The independently selectable =LambdaAitiaModuleSdlcProof= library mirrors
+=modules/sdlc/design.ss=. Its =Design= module proves reflexivity and
+transitivity of the declaration-level assumption/guarantee replacement
+direction and shows that an added assumption or dropped guarantee blocks that
+relation. It does not certify natural-language clause meanings, the Scheme
+comparison implementation or any Runtime effect.
+
+=AxiomAudit.lean= prints the transitive axioms of every exported law. The
+=just check-proof= and CI gates reject =sorryAx= in that output; a successful
+compile alone is not accepted as evidence that theorems contain no admitted
+holes.
diff --git a/proof/lean/lakefile.lean b/proof/lean/lakefile.lean
index e320cef..e9cc7bb 100644
--- a/proof/lean/lakefile.lean
+++ b/proof/lean/lakefile.lean
@@ -19,3 +19,6 @@ explicit repository-wide integration aggregate.
@[default_target]
lean_lib LambdaAitiaModuleAssuranceProof where
roots := #[`LambdaAitiaProof.Modules.Assurance]
+
+lean_lib LambdaAitiaModuleSdlcProof where
+ roots := #[`LambdaAitiaProof.Modules.Sdlc]
diff --git a/proof/quint/AssuranceLifecycle.qnt b/proof/quint/AssuranceLifecycle.qnt
new file mode 100644
index 0000000..a70a685
--- /dev/null
+++ b/proof/quint/AssuranceLifecycle.qnt
@@ -0,0 +1,130 @@
+// SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+// SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+module AssuranceLifecycle {
+ import EffectAdmission.* from "./EffectAdmission"
+
+ // One obligation, two source revisions, four clock values, three epochs.
+ type Lifecycle = {revision: int, planned: int, verified: int, admitted: int,
+ decided: int, granted: int, grantEpoch: int, epoch: int, clock: int,
+ sealUntil: int, grantUntil: int, adapterInstalled: bool,
+ reportedSuccess: bool, effectStarted: bool, startSafe: bool,
+ reservationRevision: int, reservationEpoch: int, reservationUntil: int,
+ deliveryStatus: int, deliveryCalls: int, deliverySafe: bool}
+ var q: Lifecycle
+ pure val Initial = {revision: 1, planned: 0, verified: 0, admitted: 0,
+ decided: 0, granted: 0, grantEpoch: 0, epoch: 0, clock: 0,
+ sealUntil: 0, grantUntil: 0, adapterInstalled: false,
+ reportedSuccess: false, effectStarted: false, startSafe: true,
+ reservationRevision: 0, reservationEpoch: 0, reservationUntil: 0,
+ deliveryStatus: 0, deliveryCalls: 0, deliverySafe: true}
+ action init = q' = Initial
+ action InstallAdapter = all {not(q.adapterInstalled), q' = {...q, adapterInstalled: true}}
+ action ReportSuccess = all {not(q.reportedSuccess), q' = {...q, reportedSuccess: true}}
+ action Plan = all {q.planned != q.revision, q' = {...q, planned: q.revision}}
+ action Verify = all {q.adapterInstalled, q.planned == q.revision, q.clock < 3,
+ q' = {...q, verified: q.revision, sealUntil: q.clock + 1}}
+ action Admit = all {q.verified == q.revision, q.clock < q.sealUntil,
+ q.admitted != q.revision, q' = {...q, admitted: q.revision}}
+ action Decide = all {q.admitted == q.revision, q.decided != q.revision,
+ q' = {...q, decided: q.revision}}
+ action Authorize = all {q.decided == q.revision, q.admitted == q.revision,
+ q.granted != q.revision, q.clock < 3,
+ q' = {...q, granted: q.revision, grantEpoch: q.epoch, grantUntil: q.clock + 1}}
+ val Current = currentGrant(q.granted, q.revision, q.admitted, q.decided,
+ q.grantEpoch, q.epoch, q.clock, q.grantUntil)
+ action StartEffect = all {not(q.effectStarted), Current,
+ q' = {...q, effectStarted: true,
+ reservationRevision: q.revision, reservationEpoch: q.grantEpoch,
+ reservationUntil: q.grantUntil,
+ startSafe: currentGrant(q.granted, q.revision, q.admitted, q.decided,
+ q.grantEpoch, q.epoch, q.clock, q.grantUntil)}}
+ // Start is a one-shot reservation, not delivery or an acknowledgement.
+ val DeliveryCurrent = Current and q.reservationRevision == q.revision and
+ q.reservationEpoch == q.epoch and q.clock < q.reservationUntil
+ // 0 = pending/not-started, 1 = acknowledged, 2 = rejected, 3 = failed,
+ // 4 = unknown acknowledgement. Unknown never authorizes a blind retry.
+ action DeliverEffect = all {q.effectStarted, q.deliveryStatus == 0, DeliveryCurrent,
+ q' = {...q, deliveryStatus: 1, deliveryCalls: q.deliveryCalls + 1, deliverySafe: true}}
+ action RejectDelivery = all {q.effectStarted, q.deliveryStatus == 0, not(DeliveryCurrent),
+ q' = {...q, deliveryStatus: 2}}
+ action FailDelivery = all {q.effectStarted, q.deliveryStatus == 0, DeliveryCurrent,
+ q' = {...q, deliveryStatus: 3, deliveryCalls: q.deliveryCalls + 1}}
+ action UnknownDelivery = all {q.effectStarted, q.deliveryStatus == 0, DeliveryCurrent,
+ q' = {...q, deliveryStatus: 4, deliveryCalls: q.deliveryCalls + 1}}
+ action ChangeSource = all {q.revision == 1,
+ q' = {...q, revision: 2, planned: 0, verified: 0, admitted: 0,
+ decided: 0, granted: 0, sealUntil: 0, grantUntil: 0}}
+ action Tick = all {q.clock < 3,
+ q' = {...q, clock: q.clock + 1,
+ admitted: if (q.clock + 1 < q.sealUntil) q.admitted else 0,
+ decided: if (q.clock + 1 < q.sealUntil) q.decided else 0,
+ granted: if (q.clock + 1 < q.sealUntil and q.clock + 1 < q.grantUntil)
+ q.granted else 0}}
+ action Revoke = all {q.epoch < 2,
+ q' = {...q, epoch: q.epoch + 1, verified: 0, admitted: 0,
+ decided: 0, granted: 0, sealUntil: 0}}
+ action stableStep = any {InstallAdapter, ReportSuccess, Plan, Verify, Admit,
+ Decide, Authorize, StartEffect, DeliverEffect}
+ action Terminal = all {q.clock == 3, q.epoch == 2, q.revision == 2,
+ q.adapterInstalled, q.reportedSuccess, q.planned == q.revision,
+ (not(q.effectStarted) or q.deliveryStatus != 0), q' = q}
+ action step = any {stableStep, ChangeSource, Tick, Revoke, RejectDelivery,
+ FailDelivery, UnknownDelivery, Terminal}
+
+ // Each mutation is isolated by selecting its own step operator.
+ action UnsafeStart = all {q.reportedSuccess, not(q.effectStarted),
+ q' = {...q, effectStarted: true, startSafe: Current}}
+ action UnsafeLateAdmit = all {q.revision == 2, q.reportedSuccess,
+ q.admitted == 0, q' = {...q, admitted: 1}}
+ action UnsafeEarlyGrant = all {q.revision == 2, q.reportedSuccess,
+ q.admitted == 0, q.granted == 0, q.clock < 3,
+ q' = {...q, granted: q.revision, grantEpoch: q.epoch,
+ grantUntil: q.clock + 1}}
+ action UnsafeAfterRevoke = all {q.epoch > q.grantEpoch,
+ q.grantUntil > q.clock, q.granted == 0, not(q.effectStarted),
+ q' = {...q, effectStarted: true, startSafe: Current}}
+ action unsafeStep = any {step, UnsafeStart}
+ action lateAdmitStep = any {step, UnsafeLateAdmit}
+ action earlyGrantStep = any {step, UnsafeEarlyGrant}
+ action afterRevokeStep = any {step, UnsafeAfterRevoke}
+ action UnsafeDelivery = all {q.effectStarted, q.deliveryStatus == 0, not(DeliveryCurrent),
+ q' = {...q, deliveryStatus: 1, deliveryCalls: q.deliveryCalls + 1, deliverySafe: false}}
+ action UnsafeBeforeStart = all {q.reportedSuccess, not(q.effectStarted),
+ q' = {...q, deliveryStatus: 1, deliveryCalls: q.deliveryCalls + 1, deliverySafe: false}}
+ action UnsafeReplay = all {q.deliveryStatus == 1,
+ q' = {...q, deliveryCalls: q.deliveryCalls + 1}}
+ action unsafeDeliveryStep = any {step, UnsafeDelivery}
+ action beforeStartStep = any {step, UnsafeBeforeStart}
+ action replayStep = any {step, UnsafeReplay}
+
+ val TypeOK = q.revision >= 1 and q.revision <= 2 and
+ q.planned >= 0 and q.planned <= 2 and q.verified >= 0 and q.verified <= 2 and
+ q.admitted >= 0 and q.admitted <= 2 and q.decided >= 0 and q.decided <= 2 and
+ q.granted >= 0 and q.granted <= 2 and q.grantEpoch >= 0 and q.grantEpoch <= 2 and
+ q.epoch >= 0 and q.epoch <= 2 and q.clock >= 0 and q.clock <= 3 and
+ q.sealUntil >= 0 and q.sealUntil <= 4 and q.grantUntil >= 0 and q.grantUntil <= 4 and
+ q.reservationRevision >= 0 and q.reservationRevision <= 2 and
+ q.reservationEpoch >= 0 and q.reservationEpoch <= 2 and
+ q.reservationUntil >= 0 and q.reservationUntil <= 4 and
+ q.deliveryStatus >= 0 and q.deliveryStatus <= 4 and q.deliveryCalls >= 0 and q.deliveryCalls <= 1
+ val AdmissionIsCurrent = q.admitted == 0 or
+ (q.admitted == q.revision and q.verified == q.revision and q.clock < q.sealUntil)
+ val DecisionRequiresAdmission = q.decided == 0 or
+ (q.decided == q.revision and q.admitted == q.revision)
+ val GrantRequiresCurrentDecision = q.granted == 0 or
+ (q.granted == q.revision and q.decided == q.revision and
+ q.admitted == q.revision and q.grantEpoch == q.epoch and q.clock < q.grantUntil)
+ val NoUnsafeEffectStart = q.startSafe
+ val NoEffectStarted = not(q.effectStarted)
+ val NoUnsafeDelivery = q.deliverySafe
+ val DeliveryRequiresStart = q.deliveryCalls == 0 or q.effectStarted
+ val AtMostOneDelivery = q.deliveryCalls <= 1
+
+ temporal stableFairness = InstallAdapter.weakFair(q) and Plan.weakFair(q) and
+ Verify.weakFair(q) and Admit.weakFair(q) and Decide.weakFair(q) and
+ Authorize.weakFair(q) and StartEffect.weakFair(q) and DeliverEffect.weakFair(q)
+ temporal stableSpec = q == Initial and stableStep.orKeep(q).always() and stableFairness
+ temporal stableSpecNoFair = q == Initial and stableStep.orKeep(q).always()
+ temporal EventuallyEffect = q.effectStarted.eventually()
+ temporal EventuallyDelivery = (q.deliveryStatus == 1).eventually()
+}
diff --git a/proof/quint/EffectAdmission.qnt b/proof/quint/EffectAdmission.qnt
new file mode 100644
index 0000000..515d640
--- /dev/null
+++ b/proof/quint/EffectAdmission.qnt
@@ -0,0 +1,8 @@
+// SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+// SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+module EffectAdmission {
+ pure def currentGrant(granted: int, revision: int, admitted: int, decided: int,
+ grantEpoch: int, epoch: int, clock: int, grantUntil: int): bool =
+ granted == revision and admitted == revision and decided == revision and
+ grantEpoch == epoch and clock < grantUntil
+}
diff --git a/proof/quint/README.org b/proof/quint/README.org
new file mode 100644
index 0000000..df04332
--- /dev/null
+++ b/proof/quint/README.org
@@ -0,0 +1,178 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Quint design-model qualification
+
+:PROPERTIES:
+:STATUS: design-validation-partial
+:DATE: 2026-10-11
+:END:
+
+* Model boundaries
+
+The maintained model source is Quint 0.33.0. =WorkCore.qnt= shares the
+cancellation/completion distinction; =EffectAdmission.qnt= shares the current
+grant predicate. Four finite models now check 25 contracts (including two
+temporal cases), extending the former TLA+ lane's 22 cases:
+
+| Lane | Safe check | Required negative/reachability controls |
+|------+------------+-----------------------------------------|
+| work | cancellation does not drain active or queued work | cancellation and quiescence reachable |
+| retirement | final cut requires quiescence and known inventory | cancellation-as-completion, stale review, unknown inventory, retirement reachable |
+| ingress | retirement fences epoch and rejects later stale authority | unfenced retirement, cached grant, rejection and valid start reachable |
+| lifecycle | current admission, decision, grant, one-shot reservation and delivery | untrusted report, late admission, early grant, after-revoke start, stale delivery, delivery before start, replay, valid start reachable, no-fair delivery counterexample |
+
+The =check_contract.py= runner checks all reachable finite states through
+Quint's TLC backend, requires a completed queue for safe cases, and requires
+the expected invariant failure and actual trace for each negative control.
+The lifecycle's weak-fairness case is checked by =check_progress.py=: it compiles
+the Quint source to a temporary TLA+ artifact and asks TLC to check
+=EventuallyDelivery= under the Quint-defined =stableSpec=. The same property
+fails under =stableSpecNoFair=. Generated TLA+ is a checker intermediate, not
+a second maintained model. Quint's ordinary =verify --temporal= path cannot
+select that fairness-bearing specification as TLC's =SPECIFICATION=.
+
+#+begin_src sh
+npm ci --prefix proof/quint
+AITIA_QUINT_BIN="$PWD/proof/quint/node_modules/.bin/quint" just check-quint
+AITIA_QUINT_BIN="$PWD/proof/quint/node_modules/.bin/quint" just check-quint ingress
+#+end_src
+
+The lockfile pins Quint 0.33.0 and its npm transitive graph. The checker
+requires Apalache 0.62.1's TLC backend, Java and a local loopback endpoint.
+Each check prints its name and result; a tool error or a stall is not a
+successful counterexample. No implicit dependency on the POO Flow ASCENT pin
+is introduced here: ASCENT belongs to the parent POO Flow dependency graph.
+
+* Claim boundary
+
+** Executable correspondence cut
+
+=just check-quint-scheme= runs Quint 0.33.0's TypeScript evaluator with native
+=--mbt= action metadata and ITF traces. A fixed seed (34), 512 samples and an
+18-step bound currently cover 177 of the TLC lane's 248 reachable ingress
+states and all 13 safe actions. This is reproducible sampled correspondence,
+not exhaustive Scheme refinement. The runner fails if sampled state coverage
+falls below 177, or any safe action or the post-retirement rejection witness
+is missing. Every projected field is checked
+after every action against the actual serialized Scheme Host; effect starts
+also check a real callback counter. Model schema changes fail closed.
+
+Two separately generated invariant-failure traces exercise =RetireWithoutFence=
+and =TrustCachedGrant=. Their safe prefixes must match Scheme, while the final
+unsafe transition must be rejected with no effect callback. A tool failure is
+not a negative control. ITF is translated to closed data, never evaluated as
+Scheme code. The tests retain Gerbil's native testing lifecycle and heap/time
+bounds. =check-formal= includes source binding, Host controls, MBT replay,
+exhaustive Quint checks and Lean checks; CI runs these distinct evidence lanes.
+
+=just check-lifecycle-scheme= exhausts the lifecycle TLC graph, requires its
+complete 19,042-state queue and 21-field schema, then replays 3,084 covering
+paths through real Scheme APIs. A DAG path cover plus real init prefixes covers every reachable
+state, plus all 16 named safe actions. Every field is compared after every
+step; model output is never used to manufacture Scheme receipts. This is
+complete bounded state coverage, not replay of all 62,345 graph edges or a
+general refinement proof. Renewal and terminal stutters remain explicit
+API witnesses. The cover executes 64,206 state steps; each native batch is
+bounded by 160 actual steps rather than a fixed number of traces. A single
+oversized trace fails instead of being split or given more time. Four native
+processes retain the existing per-test 1G heap / 60-second bound; failed batches,
+missing final =OK=, incomplete
+graphs, unknown actions and missing fault traces fail closed.
+
+Seven additional traces start at reachable states selected from that graph.
+Quint executes the original unsafe operator and must produce an actual
+invariant-failure ITF trace. Scheme must match the safe prefix, refuse the
+final unsafe operation, preserve the projected state and execute no delivery
+callback. This covers report-as-authority, stale admission, early grant,
+after-revoke start, stale delivery, pre-start delivery and delivery replay.
+The gate prints SHA-256 hashes of its declared model / Scheme source cut and
+rejects source changes during the run.
+Failed native inputs and partial diagnostics are preserved under
+=.cache/lifecycle-failures/=; the shared checker watchdog terminates the owned
+process group, not just its immediate launcher. Completion counters are
+emitted only after processing a real trace and do not replace final =OK= / exit status.
+
+| Lifecycle stage | Actual Scheme operation | Authority / effect observation |
+|-----------------+-------------------------+--------------------------------|
+| Plan | =assurance-plan-verification= | selects the real obligation, no authority |
+| Verify | =assurance-host-verify= | configured verifier runs and privately issues a seal |
+| Admit | =assurance-host-admit-verified= | exact current seal, no verifier rerun or expiry renewal |
+| Decide | =assurance-host-preflight-decision= | current admission becomes a preflight, not permission |
+| Authorize | =assurance-host-cedar-request= / =assurance-effect-host-authorize!= | exact current request plus configured permit issues a private grant |
+| StartEffect | =assurance-effect-host-reserve!= | consumes grant once; callback count stays zero |
+| Delivery | =assurance-effect-host-deliver!= | rechecks source / epoch / strict expiry; acknowledged, rejected, failed or unknown |
+
+=lifecycle-boundary-test.ss= also checks deny / indeterminate authorization
+(refusal stutters, never a grant), copied or foreign seals / requests / grants
+/ reservations, intent substitution, mutation of an actually issued token,
+in-place request intent / action / handoff payload mutation,
+observed source ABA, expiry exactly at the boundary, source change during
+authorization, throwing authorization / delivery, reentrancy and blind retry
+after failure or unknown acknowledgement. Unknown acknowledgement may coexist
+with a service-side effect, but is never reported as delivered or retry authority.
+Renewing verification or issuing a fresh grant cannot extend an existing
+reservation's source / epoch / deadline binding.
+
+| Quint cut | Scheme evidence | Remaining boundary |
+|-----------+-----------------+--------------------|
+| WorkCore / WorkLifecycle | cancellation, completion and queue observations in ingress replay; isolated queued-work refusal | fair completion is an environment assumption |
+| RetirementReview | review, late activity, inventory loss/recovery and retirement in replay; missing-review / unknown-inventory controls | inventory truth is supplied by the local caller |
+| RetirementIngress / EffectAdmission | 13 action projections, sampled state correspondence, unsafe-model refusals and real callbacks | one owner, cached grant, request and attempt only |
+| AssuranceLifecycle | all 19,042 projected states; 16 safe actions; seven actual unsafe-model refusals; additional authorization / delivery unit controls | one obligation / attempt, serialized local adapter assumptions; no native Cedar evaluator or deployed delivery qualification |
+
+The Host exception regression additionally verifies that a throwing callback
+propagates its failure and clears the transient handoff fence, without marking
+work complete or permitting a second start. Exceptions, continuation capture
+and process crashes are not transitions of the current Quint ingress model.
+
+** Qualification limits
+
+These are bounded design experiments, not generated projections of the POO
+Contract, proof of Scheme/Runtime refinement, or deployed authority. A separate
+local Scheme Host in =modules/sdlc/retirement-host.ss= executes the bounded
+ingress fence/start cut, including one real effect callback, and
+=t/formal/retirement-execution-test.ss= checks selected positive and negative
+behaviors. This is an executable correspondence check, not a general refinement
+proof or a deployed authority integration. The lifecycle bounds are two source revisions, four clock values
+and three epochs. Ingress isolates one cached grant, one request and one
+attempt; it does not model new grant issuance, multiple clients, persistence,
+crash recovery or inventory truth. Fair progress assumes a stable source and
+clock, no revocation, and weak fairness of each Host stage. It says nothing
+about progress under a changing environment.
+
+The lifecycle effect Host is an explicit local composition adapter, not a new
+Cedar policy engine or native Runtime Host. It composes existing POO Flow
+semantic-root / token validation primitives with private exact-identity
+issuance and one-shot reservation indexes. The configured authorization
+operation must evaluate policy for the frozen request; the configured delivery
+operation owns the actual effect and acknowledgement. The model's successful
+handoff assumes trusted, synchronous callbacks under one serialized owner;
+it does not prove atomic source / authority revalidation at a native service's
+physical effect linearization point. Deferred work or continuation reuse is
+outside this adapter contract. The lifecycle fixture supplies declared source
+snapshot metadata and a controlled verifier, not a proof of verifier truth or
+filesystem bytes. The existing evidence-admission tests remain a separate
+source-reader qualification lane. The model's successful
+authorization and fair delivery assume permit and eventual acknowledgement;
+refusal, failed and unknown outcomes do not satisfy that progress premise.
+The grant's synthetic local binding is not a native buffer / session capability.
+Pure Cedar decisions, token-shaped objects and editable delivery receipts have
+no issuing authority. A delivery receipt's =durable?= and
+=distributed-authority?= are explicitly false. Strict expiry requires
+=now < expires-at= even though the underlying pure token validator admits its
+inclusive endpoint.
+Strict expiry is rechecked at the final token-validation instant after source
+validation; a clock crossing the deadline within reserve or deliver rejects
+without a delivery callback. A native boundary control covers both operations
+with an advancing effect clock. This is additional within-operation coverage,
+not an asynchronous or physical-effect linearization proof.
+Native dual-engine Cedar evaluation, distributed
+deduplication, continuations, persistence and crash recovery are not qualified
+by this local test cut.
+
+The retirement binding test hashes the declared Lean, Quint and Scheme source
+cut. A current mapping alone is not semantic correspondence; the execution
+test supplies bounded behavioral evidence for this particular Host. Multi-owner
+concurrency, crash recovery, new grant issuance, and complete implementation
+conformance remain open.
diff --git a/proof/quint/RetirementIngress.qnt b/proof/quint/RetirementIngress.qnt
new file mode 100644
index 0000000..12b1cec
--- /dev/null
+++ b/proof/quint/RetirementIngress.qnt
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+// SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+module RetirementIngress {
+ import WorkCore.* from "./WorkCore"
+ import EffectAdmission.* from "./EffectAdmission"
+
+ type Ingress = {work: Work, known: bool, reviewed: bool, retired: bool,
+ lateFound: bool, gapFound: bool, ingressOpen: bool, epoch: int,
+ grantEpoch: int, pending: bool, attempted: bool,
+ rejectedAfterRetirement: bool, startedAfterRetirement: bool}
+ var q: Ingress
+ action init = q' = {work: {active: true, queued: true, cancelRequested: false},
+ known: false, reviewed: false, retired: false, lateFound: false, gapFound: false,
+ ingressOpen: true, epoch: 0, grantEpoch: 0, pending: false, attempted: false,
+ rejectedAfterRetirement: false, startedAfterRetirement: false}
+
+ action RequestCancellation = all {not(q.retired), not(q.work.cancelRequested),
+ q' = {...q, work: cancel(q.work)}}
+ action ObserveCompletion = all {not(q.retired), q.work.active,
+ q' = {...q, work: complete(q.work)}}
+ action ObserveQueueDrained = all {not(q.retired), q.work.queued,
+ q' = {...q, work: drain(q.work)}}
+ action CompleteInventory = all {not(q.retired), not(q.known), q' = {...q, known: true}}
+ action ReviewCut = all {not(q.retired), not(q.reviewed), quiescent(q.work), q.known,
+ q' = {...q, reviewed: true}}
+ action ObserveLateActivity = all {not(q.retired), q.reviewed, not(q.lateFound),
+ q' = {...q, work: {...q.work, active: true}, lateFound: true}}
+ action ObserveInventoryGap = all {not(q.retired), q.reviewed, not(q.gapFound),
+ q' = {...q, known: false, gapFound: true}}
+ action Observe = any {RequestCancellation, ObserveCompletion, ObserveQueueDrained,
+ CompleteInventory, ReviewCut, ObserveLateActivity, ObserveInventoryGap}
+ action CloseIngress = all {q.ingressOpen, q' = {...q, ingressOpen: false, epoch: q.epoch + 1}}
+ action Request = all {not(q.pending), not(q.attempted), q' = {...q, pending: true}}
+ val Current = currentGrant(1, 1, 1, 1, q.grantEpoch, q.epoch, 0, 1)
+ action Start = all {q.pending, Current,
+ q' = {...q, work: {...q.work, active: true}, pending: false, attempted: true,
+ startedAfterRetirement: q.retired}}
+ action TrustCachedGrant = all {q.pending,
+ q' = {...q, work: {...q.work, active: true}, pending: false, attempted: true,
+ startedAfterRetirement: q.retired}}
+ action Reject = all {q.pending, not(Current),
+ q' = {...q, pending: false, attempted: true, rejectedAfterRetirement: q.retired}}
+ action Retire = all {not(q.ingressOpen), not(q.retired), q.reviewed,
+ quiescent(q.work), q.known, q' = {...q, retired: true}}
+ action RetireWithoutFence = all {not(q.retired), q.reviewed,
+ quiescent(q.work), q.known, q' = {...q, retired: true}}
+ action Terminal = all {q.retired, q.attempted, q' = q}
+ action Environment = any {Observe, CloseIngress, Request, Reject, Terminal}
+ action step = any {Environment, Start, Retire}
+ action unfencedStep = any {Environment, Start, RetireWithoutFence}
+ action cachedGrantStep = any {Environment, TrustCachedGrant, Retire}
+
+ val TypeOK = q.epoch >= 0 and q.epoch <= 1 and q.grantEpoch == 0
+ val RetiredIsFenced = q.retired implies (not(q.ingressOpen) and q.grantEpoch != q.epoch)
+ val NoPostRetirementStart = not(q.startedAfterRetirement)
+ val NoOutstandingWork = q.retired implies quiescent(q.work)
+ val InventoryKnown = q.retired implies q.known
+ val NeverRejectAfterRetirement = not(q.rejectedAfterRetirement)
+ val NeverStarted = not(q.attempted and not(q.rejectedAfterRetirement) and q.epoch == 0)
+}
diff --git a/proof/quint/RetirementReview.qnt b/proof/quint/RetirementReview.qnt
new file mode 100644
index 0000000..8b1a7ff
--- /dev/null
+++ b/proof/quint/RetirementReview.qnt
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+// SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+module RetirementReview {
+ import WorkCore.* from "./WorkCore"
+
+ type Review = {work: Work, known: bool, reviewed: bool, retired: bool,
+ lateFound: bool, gapFound: bool}
+ var q: Review
+ action init = q' = {work: {active: true, queued: true, cancelRequested: false},
+ known: false, reviewed: false, retired: false, lateFound: false, gapFound: false}
+
+ action RequestCancellation = all {not(q.retired), not(q.work.cancelRequested),
+ q' = {...q, work: cancel(q.work)}}
+ action ObserveCompletion = all {not(q.retired), q.work.active,
+ q' = {...q, work: complete(q.work)}}
+ action ObserveQueueDrained = all {not(q.retired), q.work.queued,
+ q' = {...q, work: drain(q.work)}}
+ action CompleteInventory = all {not(q.retired), not(q.known), q' = {...q, known: true}}
+ action ReviewCut = all {not(q.retired), not(q.reviewed), quiescent(q.work), q.known,
+ q' = {...q, reviewed: true}}
+ action ObserveLateActivity = all {not(q.retired), q.reviewed, not(q.lateFound),
+ q' = {...q, work: {...q.work, active: true}, lateFound: true}}
+ action ObserveInventoryGap = all {not(q.retired), q.reviewed, not(q.gapFound),
+ q' = {...q, known: false, gapFound: true}}
+ action Observe = any {RequestCancellation, ObserveCompletion, ObserveQueueDrained,
+ CompleteInventory, ReviewCut, ObserveLateActivity, ObserveInventoryGap}
+ action Retire = all {not(q.retired), q.reviewed, quiescent(q.work), q.known,
+ q' = {...q, retired: true}}
+ action TrustCancellation = all {not(q.retired), q.work.cancelRequested, q.known,
+ q' = {...q, retired: true}}
+ action TrustOldReview = all {not(q.retired), q.reviewed, q.known,
+ q' = {...q, retired: true}}
+ action IgnoreInventoryGap = all {not(q.retired), q.reviewed, quiescent(q.work),
+ q' = {...q, retired: true}}
+ action Terminal = all {q.retired, q' = q}
+ action step = any {Observe, Retire, Terminal}
+ action cancellationStep = any {Observe, TrustCancellation, Terminal}
+ action staleStep = any {Observe, TrustOldReview, Terminal}
+ action unknownStep = any {Observe, IgnoreInventoryGap, Terminal}
+
+ val TypeOK = true
+ val NoOutstandingWork = q.retired implies quiescent(q.work)
+ val InventoryKnown = q.retired implies q.known
+ val NeverRetired = not(q.retired)
+}
diff --git a/proof/quint/WorkCore.qnt b/proof/quint/WorkCore.qnt
new file mode 100644
index 0000000..00e058e
--- /dev/null
+++ b/proof/quint/WorkCore.qnt
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+// SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+module WorkCore {
+ type Work = {active: bool, queued: bool, cancelRequested: bool}
+ pure def cancel(w: Work): Work = {...w, cancelRequested: true}
+ pure def complete(w: Work): Work = {...w, active: false}
+ pure def drain(w: Work): Work = {...w, queued: false}
+ pure def quiescent(w: Work): bool = not(w.active) and not(w.queued)
+}
diff --git a/proof/quint/WorkLifecycle.qnt b/proof/quint/WorkLifecycle.qnt
new file mode 100644
index 0000000..7c243e4
--- /dev/null
+++ b/proof/quint/WorkLifecycle.qnt
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+// SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+module WorkLifecycle {
+ import WorkCore.* from "./WorkCore"
+ // Cancellation is a request, never an observation of completion.
+ var q: Work
+ action init = q' = {active: true, queued: true, cancelRequested: false}
+ action RequestCancellation = all {not(q.cancelRequested), q' = cancel(q)}
+ action ObserveCompletion = all {q.active, q' = complete(q)}
+ action ObserveQueueDrained = all {q.queued, q' = drain(q)}
+ action Terminal = all {quiescent(q), q.cancelRequested, q' = q}
+ action step = any {RequestCancellation, ObserveCompletion, ObserveQueueDrained, Terminal}
+ action cancellationStep = any {RequestCancellation, all {q.cancelRequested, q' = q}}
+ val TypeOK = true
+ val WorkRemains = q.active and q.queued
+ val NeverCancelled = not(q.cancelRequested)
+ val NeverQuiescent = not(quiescent(q))
+}
diff --git a/proof/quint/check_contract.py b/proof/quint/check_contract.py
new file mode 100644
index 0000000..50c9c29
--- /dev/null
+++ b/proof/quint/check_contract.py
@@ -0,0 +1,98 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""Exhaust Quint design models and require typed negative-control traces."""
+
+from __future__ import annotations
+
+import argparse
+import os
+from pathlib import Path
+import re
+import shutil
+import subprocess
+
+from process_runner import run
+
+
+ROOT = Path(__file__).resolve().parent
+QUINT = os.environ.get("AITIA_QUINT_BIN") or shutil.which("quint")
+COUNTS = re.compile(r"(\d+) states generated, (\d+) distinct states found, (\d+) states left on queue")
+SAFE_DISTINCT = {"work-safe": 8, "work-cancellation": 2,
+ "retirement-safe": 42, "ingress-safe": 248,
+ "lifecycle-safe": 19042}
+# lane, name, model, transition, invariant(s), expected counterexample
+CASES = (
+ ("work", "work-safe", "WorkLifecycle", "step", "TypeOK", False),
+ ("work", "work-cancellation", "WorkLifecycle", "cancellationStep", "WorkRemains", False),
+ ("work", "work-cancel-reachable", "WorkLifecycle", "cancellationStep", "NeverCancelled", True),
+ ("work", "work-quiescent-reachable", "WorkLifecycle", "step", "NeverQuiescent", True),
+ ("retirement", "retirement-safe", "RetirementReview", "step", "TypeOK,NoOutstandingWork,InventoryKnown", False),
+ ("retirement", "retirement-trust-cancel", "RetirementReview", "cancellationStep", "NoOutstandingWork", True),
+ ("retirement", "retirement-stale-review", "RetirementReview", "staleStep", "NoOutstandingWork", True),
+ ("retirement", "retirement-unknown-inventory", "RetirementReview", "unknownStep", "InventoryKnown", True),
+ ("retirement", "retirement-reachable", "RetirementReview", "step", "NeverRetired", True),
+ ("ingress", "ingress-safe", "RetirementIngress", "step", "TypeOK,RetiredIsFenced,NoPostRetirementStart,NoOutstandingWork,InventoryKnown", False),
+ ("ingress", "ingress-unfenced", "RetirementIngress", "unfencedStep", "NoPostRetirementStart", True),
+ ("ingress", "ingress-cached-grant", "RetirementIngress", "cachedGrantStep", "NoPostRetirementStart", True),
+ ("ingress", "ingress-reject-reachable", "RetirementIngress", "step", "NeverRejectAfterRetirement", True),
+ ("ingress", "ingress-start-reachable", "RetirementIngress", "step", "NeverStarted", True),
+ ("lifecycle", "lifecycle-safe", "AssuranceLifecycle", "step", "TypeOK,AdmissionIsCurrent,DecisionRequiresAdmission,GrantRequiresCurrentDecision,NoUnsafeEffectStart,NoUnsafeDelivery,DeliveryRequiresStart,AtMostOneDelivery", False),
+ ("lifecycle", "lifecycle-start-reachable", "AssuranceLifecycle", "step", "NoEffectStarted", True),
+ ("lifecycle", "lifecycle-untrusted-report", "AssuranceLifecycle", "unsafeStep", "NoUnsafeEffectStart", True),
+ ("lifecycle", "lifecycle-late-admission", "AssuranceLifecycle", "lateAdmitStep", "AdmissionIsCurrent", True),
+ ("lifecycle", "lifecycle-early-grant", "AssuranceLifecycle", "earlyGrantStep", "GrantRequiresCurrentDecision", True),
+ ("lifecycle", "lifecycle-after-revoke", "AssuranceLifecycle", "afterRevokeStep", "NoUnsafeEffectStart", True),
+ ("lifecycle", "lifecycle-stale-delivery", "AssuranceLifecycle", "unsafeDeliveryStep", "NoUnsafeDelivery", True),
+ ("lifecycle", "lifecycle-delivery-before-start", "AssuranceLifecycle", "beforeStartStep", "DeliveryRequiresStart", True),
+ ("lifecycle", "lifecycle-replay-delivery", "AssuranceLifecycle", "replayStep", "AtMostOneDelivery", True),
+)
+
+
+def check_case(case: tuple) -> None:
+ lane, name, model, transition, invariants, negative = case
+ print(f"QUINT-CHECK: {name}", flush=True)
+ command = [QUINT, "verify", str(ROOT / f"{model}.qnt"), "--backend", "tlc",
+ "--step", transition, "--tlc-config", str(ROOT / "tlc-config.json"),
+ "--verbosity", "3"]
+ if "," in invariants:
+ command += ["--invariants", *invariants.split(",")]
+ else:
+ command += ["--invariant", invariants]
+ status, output = run(command, ROOT, name)
+ counts = COUNTS.search(output)
+ expected_code = 1 if negative else 0
+ marker = "Error: Invariant q_inv is violated." if negative else "[ok] No violation found"
+ if (status != expected_code or marker not in output or not counts
+ or int(counts[2]) == 0 or (not negative and int(counts[3]) != 0)
+ or (name in SAFE_DISTINCT and int(counts[2]) != SAFE_DISTINCT[name])
+ or (negative and ("error: found a counterexample" not in output
+ or "behavior up to this point" not in output))):
+ raise SystemExit(f"QUINT-CHECK-FAILED: {name} exit={status}\n{output[-6000:]}")
+ print(f"QUINT-CHECK-OK: {name} distinct={counts[2]} exit={status}", flush=True)
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("lane", nargs="?", default="all",
+ choices=("all", "work", "retirement", "ingress", "lifecycle"))
+ selected = parser.parse_args().lane
+ if not QUINT:
+ parser.error("Quint executable is required")
+ version = subprocess.check_output((QUINT, "--version"), text=True, timeout=5).strip()
+ if version != "0.33.0":
+ parser.error(f"expected Quint 0.33.0, found {version!r}")
+ for case in CASES:
+ if selected in ("all", case[0]):
+ check_case(case)
+ if selected in ("all", "lifecycle"):
+ result = subprocess.run(("python3", str(ROOT / "check_progress.py")),
+ cwd=ROOT, timeout=120, check=False)
+ if result.returncode:
+ raise SystemExit(result.returncode)
+ print("QUINT-CHECK-OK: selected model contracts complete", flush=True)
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/proof/quint/check_lifecycle_replay.py b/proof/quint/check_lifecycle_replay.py
new file mode 100644
index 0000000..94f89a4
--- /dev/null
+++ b/proof/quint/check_lifecycle_replay.py
@@ -0,0 +1,322 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+"""Exhaust the Quint lifecycle graph and replay every reachable state in Scheme."""
+
+from collections import deque
+from concurrent.futures import ThreadPoolExecutor, as_completed
+import hashlib
+import json
+import os
+from pathlib import Path
+import re
+import subprocess
+import tempfile
+
+from process_runner import run
+
+ROOT = Path(__file__).resolve().parent
+REPO = ROOT.parent.parent
+ACTIONS = ("InstallAdapter", "ReportSuccess", "Plan", "Verify", "Admit", "Decide",
+ "Authorize", "StartEffect", "DeliverEffect", "ChangeSource", "Tick", "Revoke",
+ "RejectDelivery", "FailDelivery", "UnknownDelivery", "Terminal")
+FIELDS = ("revision", "planned", "verified", "admitted", "decided", "granted",
+ "grantEpoch", "epoch", "clock", "sealUntil", "grantUntil", "adapterInstalled",
+ "reportedSuccess", "effectStarted", "startSafe", "reservationRevision",
+ "reservationEpoch", "reservationUntil", "deliveryStatus", "deliveryCalls", "deliverySafe")
+INVARIANTS = ("TypeOK", "AdmissionIsCurrent", "DecisionRequiresAdmission",
+ "GrantRequiresCurrentDecision", "NoUnsafeEffectStart", "NoUnsafeDelivery",
+ "DeliveryRequiresStart", "AtMostOneDelivery")
+FAULTS = (("unsafeStep", "NoUnsafeEffectStart", "UnsafeStart"),
+ ("lateAdmitStep", "AdmissionIsCurrent", "UnsafeLateAdmit"),
+ ("earlyGrantStep", "GrantRequiresCurrentDecision", "UnsafeEarlyGrant"),
+ ("afterRevokeStep", "NoUnsafeEffectStart", "UnsafeAfterRevoke"),
+ ("unsafeDeliveryStep", "NoUnsafeDelivery", "UnsafeDelivery"),
+ ("beforeStartStep", "DeliveryRequiresStart", "UnsafeBeforeStart"),
+ ("replayStep", "AtMostOneDelivery", "UnsafeReplay"))
+SOURCE_CUT = ("proof/quint/AssuranceLifecycle.qnt", "proof/quint/EffectAdmission.qnt",
+ "proof/quint/package-lock.json", "proof/quint/check_lifecycle_replay.py",
+ "proof/quint/test_lifecycle_replay.py",
+ "proof/quint/process_runner.py",
+ "modules/assurance/host.ss", "modules/assurance/effect-host.ss",
+ "modules/assurance/interface.ss", "t/formal/lifecycle-fixture.ss",
+ "t/formal/lifecycle-replay-test.ss", "t/assurance/unit/lifecycle-boundary-test.ss")
+
+
+def source_hashes():
+ return {path: hashlib.sha256((REPO / path).read_bytes()).hexdigest() for path in SOURCE_CUT}
+
+
+def export_graph(quint, work):
+ status, output = run([quint, "compile", str(ROOT / "AssuranceLifecycle.qnt"),
+ "--target", "tlaplus", "--step", "step"], ROOT, "lifecycle-compile")
+ start = output.find("-------------------------- MODULE AssuranceLifecycle")
+ if status or start < 0 or not output.rstrip().endswith("===="):
+ raise RuntimeError(f"Quint compilation failed: {output[-2000:]}")
+ # Only a checker intermediate: all transition bodies come from Quint.
+ # Expanding the compiled step into its named leaf operators lets TLC label
+ # edges without adding a history variable / changing the state quotient.
+ model = (ROOT / "AssuranceLifecycle.qnt").read_text()
+ unions = dict(re.findall(r"action\s+(\w+)\s*=\s*any\s*\{([^}]+)\}", model))
+ def leaf_actions(name):
+ if name not in unions:
+ return {name}
+ return set().union(*(leaf_actions(part.strip()) for part in unions[name].split(",")))
+ if leaf_actions("step") != set(ACTIONS):
+ raise RuntimeError("Quint action surface changed")
+ source = re.sub(r"=+\s*$", "", output[start:])
+ source += "\nReplayNext == " + " \\/ ".join(ACTIONS) + "\n====\n"
+ (work / "AssuranceLifecycle.tla").write_text(source)
+ (work / "safe.cfg").write_text("INIT init\nNEXT ReplayNext\nINVARIANTS " + " ".join(INVARIANTS) + "\n")
+ jar = Path(os.environ.get("QUINT_HOME", str(Path.home() / ".quint"))) / "apalache-dist-0.62.1/apalache/lib/apalache.jar"
+ if not jar.is_file():
+ raise RuntimeError("pinned Quint TLC backend jar is required")
+ status, output = run(["java", "-Xmx512m", "-cp", str(jar), "tlc2.TLC", "-workers", "1",
+ "-metadir", str(work / "meta"), "-config", str(work / "safe.cfg"),
+ "-dump", "dot,actionlabels", str(work / "graph"),
+ str(work / "AssuranceLifecycle.tla")], work, "lifecycle-graph")
+ counts = re.search(r"(\d+) states generated, (\d+) distinct states found, (\d+) states left on queue", output)
+ if status or "Model checking completed. No error has been found." not in output or not counts or int(counts[3]):
+ raise RuntimeError(f"incomplete lifecycle graph: exit={status}\n{output[-3000:]}")
+ return int(counts[2]), work / "graph.dot"
+
+
+def read_graph(path, expected):
+ nodes, edges, initial = {}, [], []
+ for line in path.read_text().splitlines():
+ node = re.match(r'\s*"?(-?\d+)"?\s+\[label="([^"]*)"', line)
+ if node:
+ values = {key: (value == "TRUE" if value in ("TRUE", "FALSE") else int(value))
+ for key, value in re.findall(r"(\w+)\s*\|->\s*(TRUE|FALSE|-?\d+)", node[2])}
+ if set(values) != set(FIELDS):
+ raise ValueError(f"model state schema changed: {set(values) ^ set(FIELDS)}")
+ nodes[node[1]] = values
+ if "style = filled" in line or "style=filled" in line:
+ initial.append(node[1])
+ edge = re.match(r'\s*"?(-?\d+)"?\s*->\s*"?(-?\d+)"?.*label="([^"]+)"', line)
+ if edge:
+ label = edge[3].split("\\n")[0]
+ if label not in ACTIONS:
+ raise ValueError(f"unknown TLC action label {label!r}")
+ edges.append((edge[1], edge[2], label))
+ if len(nodes) != expected or len(initial) != 1 or not edges:
+ raise ValueError(f"incomplete graph: nodes={len(nodes)}/{expected} initial={initial} edges={len(edges)}")
+ if any(origin not in nodes or target not in nodes for origin, target, _ in edges):
+ raise ValueError("edge references a missing state")
+ return nodes, edges, initial[0]
+
+
+def covering_traces(nodes, edges, initial):
+ adjacency = {}
+ for origin, target, action in edges:
+ adjacency.setdefault(origin, []).append((target, action))
+ paths = {initial: [("init", nodes[initial])]}
+ pending = deque([initial])
+ while pending:
+ origin = pending.popleft()
+ for target, action in adjacency.get(origin, []):
+ if target not in paths:
+ paths[target] = paths[origin] + [(action, nodes[target])]
+ pending.append(target)
+ if set(paths) != set(nodes):
+ raise ValueError("TLC dump contains unreachable or missing nodes")
+ # Ignoring self loops, this bounded lifecycle is a DAG. Its minimum vertex
+ # path cover is a bipartite maximum matching. Each path gets a real safe
+ # prefix from init, then continues through original TLC edges. No Host is
+ # cloned or reset mid-path, and no model state is synthesized in Scheme.
+ distinct = {origin: list(dict.fromkeys(target for target, _ in targets if target != origin))
+ for origin, targets in adjacency.items()}
+ indegree = dict.fromkeys(nodes, 0)
+ for targets in distinct.values():
+ for target in targets:
+ indegree[target] += 1
+ queue = deque(node for node in nodes if not indegree[node])
+ ordered = []
+ while queue:
+ origin = queue.popleft()
+ ordered.append(origin)
+ for target in distinct.get(origin, []):
+ indegree[target] -= 1
+ if not indegree[target]:
+ queue.append(target)
+ if len(ordered) != len(nodes):
+ raise ValueError("non-stuttering lifecycle graph is not a DAG")
+ left, right = {}, {}
+ def augment(origin, seen):
+ for target in distinct.get(origin, []):
+ if target in seen:
+ continue
+ seen.add(target)
+ if target not in right or augment(right[target], seen):
+ left[origin], right[target] = target, origin
+ return True
+ return False
+ for origin in nodes:
+ augment(origin, set())
+ labels = {(origin, target): action for origin, target, action in edges}
+ traces = []
+ for origin in nodes:
+ if origin in right:
+ continue
+ trace = list(paths[origin])
+ while origin in left:
+ target = left[origin]
+ trace.append((labels[origin, target], nodes[target]))
+ origin = target
+ traces.append(trace)
+ covered = {action for trace in traces for action, _ in trace}
+ stutters = {action for origin, target, action in edges if origin == target}
+ for action in ACTIONS:
+ if action not in covered and action not in stutters:
+ origin, target, _ = next(edge for edge in edges if edge[2] == action)
+ traces.append(paths[origin] + [(action, nodes[target])])
+ # Preserve renewal and terminal stutter witnesses as actual API calls.
+ for action in ACTIONS:
+ if action not in stutters:
+ continue
+ origin, target, _ = min((edge for edge in edges if edge[0] == edge[1] and edge[2] == action),
+ key=lambda edge: len(paths[edge[0]]))
+ traces.append(paths[origin] + [(action, nodes[target])])
+ if {json.dumps(state, sort_keys=True) for trace in traces for _, state in trace} != {
+ json.dumps(state, sort_keys=True) for state in nodes.values()}:
+ raise ValueError("covering paths do not cover the complete state space")
+ return traces
+
+
+def batches(traces, step_budget=160):
+ batch, steps = [], 0
+ for trace in traces:
+ if len(trace) > step_budget:
+ raise ValueError("one trace exceeds the native step budget")
+ if batch and steps + len(trace) > step_budget:
+ yield batch
+ batch, steps = [], 0
+ batch.append(trace)
+ steps += len(trace)
+ if batch:
+ yield batch
+
+
+def sexpr_trace(trace):
+ return "(" + " ".join(
+ f"({action} (" + " ".join(f"({key} {'#t' if value is True else '#f' if value is False else value})"
+ for key, value in state.items()) + "))"
+ for action, state in trace) + ")"
+
+
+def negative_traces(quint, work, safe):
+ current = lambda q: (q["granted"] == q["revision"] == q["admitted"] == q["decided"]
+ and q["grantEpoch"] == q["epoch"] and q["clock"] < q["grantUntil"])
+ selectors = {
+ "UnsafeStart": lambda q: q["reportedSuccess"] and not q["effectStarted"] and not current(q),
+ "UnsafeLateAdmit": lambda q: q["revision"] == 2 and q["reportedSuccess"] and q["admitted"] == 0,
+ "UnsafeEarlyGrant": lambda q: q["revision"] == 2 and q["reportedSuccess"] and q["admitted"] == 0 and q["granted"] == 0 and q["clock"] < 3,
+ "UnsafeAfterRevoke": lambda q: q["epoch"] > q["grantEpoch"] and q["grantUntil"] > q["clock"] and q["granted"] == 0 and not q["effectStarted"],
+ "UnsafeDelivery": lambda q: q["effectStarted"] and q["deliveryStatus"] == 0 and not current(q),
+ "UnsafeBeforeStart": lambda q: q["reportedSuccess"] and not q["effectStarted"],
+ "UnsafeReplay": lambda q: q["deliveryStatus"] == 1,
+ }
+ traces = []
+ for step, invariant, fault in FAULTS:
+ prefixes = [trace[:index + 1] for trace in safe for index, (_, state) in enumerate(trace)
+ if selectors[fault](state)]
+ if not prefixes:
+ raise RuntimeError(f"no reachable fault input for {fault}")
+ prefix = min(prefixes, key=len)
+ state = prefix[-1][1]
+ record = "{" + ", ".join(f"{key}: {str(value).lower() if isinstance(value, bool) else value}"
+ for key, value in state.items()) + "}"
+ # Select an actually reachable input, then execute the original Quint
+ # fault operator. No guessed output state or random-hit requirement.
+ source = (ROOT / "AssuranceLifecycle.qnt").read_text()
+ generated = work / f"{fault}.qnt"
+ generated.write_text(source.rstrip()[:-1] + f"\n action FaultInit = q' = {record}\n}}\n")
+ # Import resolution remains pinned to the same source cut.
+ (work / "EffectAdmission.qnt").write_text((ROOT / "EffectAdmission.qnt").read_text())
+ status, output = run([quint, "run", str(generated), "--main", "AssuranceLifecycle",
+ "--backend", "typescript", "--seed", "34", "--max-samples", "1",
+ "--n-traces", "1", "--max-steps", "1", "--mbt", "--init", "FaultInit", "--step", fault,
+ "--invariant", invariant, "--verbosity", "1",
+ "--out-itf", str(work / f"{fault}_{{seq}}.json")], REPO, fault)
+ if status != 1 or "Invariant violated" not in output:
+ raise RuntimeError(f"missing typed counterexample for {fault}: {output[-2000:]}")
+ candidates = [json.loads(path.read_text())["states"] for path in work.glob(f"{fault}_*.json")]
+ candidates = [trace for trace in candidates if trace[-1]["mbt::actionTaken"] == fault]
+ if not candidates:
+ raise RuntimeError(f"no failing ITF trace for {fault}")
+ trace = list(prefix)
+ for row in candidates[0][1:]:
+ action, state = row["mbt::actionTaken"], row["q"]
+ if action not in set(ACTIONS) | {"init", fault} or set(state) != set(FIELDS):
+ raise ValueError("negative model action/state schema changed")
+ converted = {}
+ for key, value in state.items():
+ if isinstance(value, bool):
+ converted[key] = value
+ elif isinstance(value, dict) and set(value) == {"#bigint"}:
+ converted[key] = int(value["#bigint"])
+ else:
+ raise ValueError("unexpected ITF scalar")
+ trace.append((action, converted))
+ traces.append(trace)
+ return traces
+
+
+def replay(work, chunk, traces):
+ fixture = work / f"traces-{chunk}.scm"
+ fixture.write_text("(" + "\n".join(map(sexpr_trace, traces)) + ")\n")
+ try:
+ status, output = run(["env", f"AITIA_LIFECYCLE_REPLAY={fixture}", "just",
+ "test-scheme-atomic", "formal", "lifecycle-replay-test.ss"],
+ REPO, f"native-lifecycle-{chunk}", timeout=65)
+ if status or not re.search(r"^OK$", output, re.MULTILINE):
+ raise RuntimeError(f"Scheme replay failed: chunk={chunk} exit={status}\n{output[-8000:]}")
+ except (RuntimeError, TimeoutError) as error:
+ # Keep the exact failed input after the temporary graph is removed.
+ evidence = REPO / ".cache/lifecycle-failures"
+ evidence.mkdir(parents=True, exist_ok=True)
+ saved = evidence / f"traces-{chunk}.scm"
+ saved.write_bytes(fixture.read_bytes())
+ (evidence / f"traces-{chunk}.log").write_text(str(error))
+ raise RuntimeError(f"{error}\nFAILED-LIFECYCLE-INPUT: {saved}") from error
+ return len(traces)
+
+
+def main():
+ hashes = source_hashes()
+ quint = os.environ.get("AITIA_QUINT_BIN", str(ROOT / "node_modules/.bin/quint"))
+ if subprocess.check_output([quint, "--version"], text=True, timeout=5).strip() != "0.33.0":
+ raise RuntimeError("Quint 0.33.0 is required")
+ with tempfile.TemporaryDirectory(prefix="aitia-lifecycle-") as temporary:
+ work = Path(temporary)
+ count, graph = export_graph(quint, work)
+ if count != 19042:
+ raise RuntimeError(f"lifecycle state quotient changed: {count} != 19042")
+ nodes, edges, initial = read_graph(graph, count)
+ traces = covering_traces(nodes, edges, initial)
+ covered = {edge[2] for edge in edges}
+ if covered != set(ACTIONS):
+ raise RuntimeError(f"incomplete action coverage: {covered ^ set(ACTIONS)}")
+ print(f"LIFECYCLE-GRAPH-OK: states={count} graph-edges={len(edges)} actions={len(covered)} traces={len(traces)}", flush=True)
+ faults = negative_traces(quint, work, traces)
+ replay(work, "faults", faults)
+ completed = 0
+ # Four bounded native processes; each retains the existing 1G/60s gate.
+ with ThreadPoolExecutor(max_workers=4) as pool:
+ jobs = [pool.submit(replay, work, chunk, batch)
+ for chunk, batch in enumerate(batches(traces))]
+ try:
+ for job in as_completed(jobs):
+ completed += job.result()
+ print(f"LIFECYCLE-REPLAY: traces={completed}/{len(traces)}", flush=True)
+ except BaseException:
+ for job in jobs:
+ job.cancel()
+ raise
+ if hashes != source_hashes():
+ raise RuntimeError("declared lifecycle source cut changed during verification")
+ print("LIFECYCLE-SOURCE-CUT: " + json.dumps(hashes, sort_keys=True), flush=True)
+ print(f"LIFECYCLE-SCHEME-OK: states={count}/{count} actions={len(covered)}/16 traces={completed}/{len(traces)} faults={len(faults)}/7", flush=True)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/proof/quint/check_progress.py b/proof/quint/check_progress.py
new file mode 100644
index 0000000..8517394
--- /dev/null
+++ b/proof/quint/check_progress.py
@@ -0,0 +1,77 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""Check Quint's fair and unfair lifecycle specs with TLC's native temporal lane.
+
+Quint's ``verify --temporal`` places its default transition spec beside the
+selected property; TLC rejects an implication that embeds weak-fairness actions.
+Compiling the Quint source and selecting its named spec in a TLC config keeps
+the source of truth in Quint while exercising the exact fairness formula.
+"""
+
+from __future__ import annotations
+
+import os
+from pathlib import Path
+import re
+import shutil
+import subprocess
+import tempfile
+
+from process_runner import run
+
+
+ROOT = Path(__file__).resolve().parent
+QUINT = os.environ.get("AITIA_QUINT_BIN") or shutil.which("quint")
+APALACHE = (Path(os.environ["QUINT_HOME"]) if "QUINT_HOME" in os.environ
+ else Path.home() / ".quint") / "apalache-dist-0.62.1/apalache/lib/apalache.jar"
+
+
+def main() -> int:
+ if not QUINT or not APALACHE.is_file():
+ raise SystemExit("Quint 0.33.0 and its Apalache 0.62.1 TLC jar are required")
+ version = subprocess.check_output((QUINT, "--version"), text=True, timeout=5).strip()
+ if version != "0.33.0":
+ raise SystemExit(f"expected Quint 0.33.0, found {version}")
+ print("QUINT-PROGRESS: compiling lifecycle to temporary TLA+", flush=True)
+ compiled_status, compiled_output = run(
+ (QUINT, "compile", str(ROOT / "AssuranceLifecycle.qnt"),
+ "--target", "tlaplus", "--step", "stableStep",
+ "--temporal", "EventuallyDelivery"),
+ ROOT, "quint-compile",
+ )
+ module_start = compiled_output.find("-------------------------- MODULE AssuranceLifecycle")
+ source = compiled_output[module_start:] if module_start >= 0 else ""
+ if compiled_status or module_start < 0 or not source.rstrip().endswith("===="):
+ raise SystemExit(f"Quint compilation failed: {compiled_output[-3000:]}")
+ with tempfile.TemporaryDirectory(prefix="aitia-quint-progress-") as directory:
+ work = Path(directory)
+ (work / "AssuranceLifecycle.tla").write_text(source)
+ for name, spec, expected_code, marker in (
+ ("fair", "stableSpec", 0, "Model checking completed. No error has been found."),
+ ("no-fair", "stableSpecNoFair", 13, "Error: Temporal property EventuallyDelivery was violated."),
+ ):
+ print(f"QUINT-PROGRESS: checking {name}", flush=True)
+ config = work / f"{name}.cfg"
+ config.write_text(f"SPECIFICATION {spec}\nPROPERTY EventuallyDelivery\n")
+ meta = work / f"meta-{name}"
+ meta.mkdir()
+ status, output = run(
+ ("java", "-XX:+UseParallelGC", "-Xmx512m", "-cp", str(APALACHE),
+ "tlc2.TLC", "-workers", "1", "-metadir", str(meta),
+ "-config", str(config), str(work / "AssuranceLifecycle.tla")),
+ work, f"quint-progress-{name}",
+ )
+ counts = re.search(r"(\d+) states generated, (\d+) distinct states found, (\d+) states left on queue", output)
+ if status != expected_code or marker not in output or not counts:
+ raise SystemExit(f"QUINT-PROGRESS-FAILED: {name} exit={status}\n{output[-5000:]}")
+ if int(counts[2]) != 20 or int(counts[3]) != 0:
+ raise SystemExit(f"QUINT-PROGRESS-FAILED: unexpected {name} state space")
+ if expected_code and "counter-example" not in output:
+ raise SystemExit(f"QUINT-PROGRESS-FAILED: no counterexample for {name}")
+ print(f"QUINT-PROGRESS-OK: {name} distinct={counts[2]}", flush=True)
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/proof/quint/check_scheme_replay.py b/proof/quint/check_scheme_replay.py
new file mode 100644
index 0000000..00c5d24
--- /dev/null
+++ b/proof/quint/check_scheme_replay.py
@@ -0,0 +1,128 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""Replay pinned Quint MBT traces through the actual serialized Scheme Host.
+
+Sampling is not exhaustive refinement. Require all safe actions and both real
+unsafe-model counterexamples; compare every projected state, not just outcomes.
+"""
+
+from __future__ import annotations
+
+import json
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import tempfile
+
+from process_runner import run
+
+ROOT = Path(__file__).resolve().parent
+REPO = ROOT.parent.parent
+ACTIONS = {"RequestCancellation", "ObserveCompletion", "ObserveQueueDrained",
+ "CompleteInventory", "ReviewCut", "ObserveLateActivity",
+ "ObserveInventoryGap", "CloseIngress", "Request", "Start", "Reject",
+ "Retire", "Terminal"}
+FIELDS = (("active?", "work", "active"), ("queued?", "work", "queued"),
+ ("cancellation-requested?", "work", "cancelRequested"),
+ ("inventory-known?", "known"), ("reviewed?", "reviewed"),
+ ("late-activity-found?", "lateFound"), ("inventory-gap-found?", "gapFound"),
+ ("retired?", "retired"), ("ingress-open?", "ingressOpen"),
+ ("authority-epoch", "epoch"), ("cached-grant-epoch", "grantEpoch"),
+ ("pending?", "pending"), ("attempted?", "attempted"))
+
+
+def scalar(value):
+ if isinstance(value, bool):
+ return "#t" if value else "#f"
+ if isinstance(value, dict) and set(value) == {"#bigint"}:
+ return str(int(value["#bigint"]))
+ raise ValueError(f"unexpected ITF scalar: {value!r}")
+
+
+def state_row(state):
+ q = state["q"]
+ expected_keys = {path[1] for path in FIELDS} | {"rejectedAfterRetirement", "startedAfterRetirement"}
+ if set(q) != expected_keys or set(q["work"]) != {"active", "queued", "cancelRequested"}:
+ raise ValueError("model state schema changed; update the explicit correspondence")
+ fields = []
+ for slot, *path in FIELDS:
+ value = q
+ for key in path:
+ value = value[key]
+ fields.append(f"({slot} {scalar(value)})")
+ # MBT action names are a closed set, never arbitrary executable Scheme.
+ action = state["mbt::actionTaken"]
+ if action not in ACTIONS | {"init", "TrustCachedGrant", "RetireWithoutFence"}:
+ raise ValueError(f"unknown Quint action: {action!r}")
+ return f"({action} ({' '.join(fields)}))"
+
+
+def generate(quint, directory, name, step, invariant, samples):
+ print(f"QUINT-SCHEME: generate {name}", flush=True)
+ negative = step != "step"
+ status, output = run(
+ [quint, "run", str(ROOT / "RetirementIngress.qnt"), "--backend", "typescript",
+ "--seed", "34", "--max-samples", str(samples),
+ "--n-traces", "1" if negative else str(samples),
+ "--max-steps", "18", "--mbt", "--step", step, "--invariant", invariant,
+ "--verbosity", "1", "--out-itf", str(directory / f"{name}_{{seq}}.json")],
+ REPO, name)
+ marker = "Invariant violated" if negative else "[ok] No violation found"
+ if status != int(negative) or marker not in output:
+ raise RuntimeError(f"{name}: unexpected Quint result exit={status}\n{output[-2000:]}")
+ traces = [json.loads(path.read_text())["states"]
+ for path in sorted(directory.glob(f"{name}_*.json"))]
+ if not traces or (not negative and len(traces) != samples):
+ raise RuntimeError(f"{name}: incomplete trace output ({len(traces)})")
+ if negative:
+ # Quint may retain passing samples too. Only actual failing traces
+ # belong to the negative lane; their final unsafe action is refused.
+ fault = "RetireWithoutFence" if step == "unfencedStep" else "TrustCachedGrant"
+ traces = [trace for trace in traces if trace[-1]["mbt::actionTaken"] == fault
+ and (trace[-1]["q"]["retired"] if fault == "RetireWithoutFence"
+ else trace[-1]["q"]["startedAfterRetirement"])]
+ if not traces:
+ raise RuntimeError(f"{name}: missing typed unsafe counterexample")
+ return traces
+
+
+def main():
+ quint = os.environ.get("AITIA_QUINT_BIN") or shutil.which("quint")
+ if not quint:
+ quint = str(ROOT / "node_modules/.bin/quint")
+ version = subprocess.check_output([quint, "--version"], text=True, timeout=5).strip()
+ if version != "0.33.0":
+ raise RuntimeError(f"expected Quint 0.33.0, found {version!r}")
+ with tempfile.TemporaryDirectory(prefix="aitia-quint-replay-") as temporary:
+ directory = Path(temporary)
+ safe = generate(quint, directory, "safe", "step",
+ "TypeOK and RetiredIsFenced and NoPostRetirementStart and NoOutstandingWork and InventoryKnown", 512)
+ covered = {state["mbt::actionTaken"] for trace in safe for state in trace[1:]}
+ if covered != ACTIONS:
+ raise RuntimeError(f"safe action coverage mismatch: {covered ^ ACTIONS}")
+ witnesses = [state["q"] for trace in safe for state in trace]
+ distinct = len({json.dumps(q, sort_keys=True) for q in witnesses})
+ if distinct < 177:
+ raise RuntimeError(f"safe state coverage regressed: {distinct} < 177")
+ if not any(q["rejectedAfterRetirement"] for q in witnesses):
+ raise RuntimeError("missing post-retirement rejection witness")
+ unfenced = generate(quint, directory, "unfenced", "unfencedStep", "RetiredIsFenced", 512)
+ cached = generate(quint, directory, "cached", "cachedGrantStep", "NoPostRetirementStart", 512)
+ lanes = (("safe", safe), ("unfenced", unfenced), ("cached", cached))
+ fixture = directory / "traces.scm"
+ fixture.write_text("(" + "\n".join(
+ f"({name} ({' '.join('(' + ' '.join(map(state_row, trace)) + ')' for trace in traces)}))"
+ for name, traces in lanes) + ")\n")
+ environment = dict(os.environ, AITIA_QUINT_REPLAY=str(fixture))
+ result = subprocess.run(["just", "test-scheme-atomic", "formal", "retirement-replay-test.ss"],
+ cwd=REPO, env=environment, timeout=65, check=False)
+ if result.returncode:
+ raise SystemExit(result.returncode)
+ print(f"QUINT-SCHEME-OK: safe-traces={len(safe)} states={distinct}/248 "
+ f"actions={len(covered)} negative-traces={len(unfenced) + len(cached)}", flush=True)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/proof/quint/package-lock.json b/proof/quint/package-lock.json
new file mode 100644
index 0000000..3abe5d9
--- /dev/null
+++ b/proof/quint/package-lock.json
@@ -0,0 +1,799 @@
+{
+ "name": "lambda-aitia-quint-checks",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "lambda-aitia-quint-checks",
+ "dependencies": {
+ "@informalsystems/quint": "0.33.0"
+ }
+ },
+ "node_modules/@grpc/grpc-js": {
+ "version": "1.14.5",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.5.tgz",
+ "integrity": "sha512-7VZM+SVdEcUUqSQeNI3zM8Qs/BhQKZndPo2h5VkYkAM8Iz0wJIa8mKV5ekQGqG8UUsnkQ0NMxIxwkIHYvj0qOw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@grpc/proto-loader": "^0.8.0",
+ "@js-sdsl/ordered-map": "^4.4.2"
+ },
+ "engines": {
+ "node": ">=12.10.0"
+ }
+ },
+ "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz",
+ "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "lodash.camelcase": "^4.3.0",
+ "long": "^5.0.0",
+ "protobufjs": "^7.5.5",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@grpc/proto-loader": {
+ "version": "0.7.15",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
+ "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "lodash.camelcase": "^4.3.0",
+ "long": "^5.0.0",
+ "protobufjs": "^7.2.5",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@informalsystems/quint": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/@informalsystems/quint/-/quint-0.33.0.tgz",
+ "integrity": "sha512-1hr+l+3wyKimjNXNl9lovpwmH5C9Ck898FL4yGbuEmFPiz7YRqvFvsB9hlHsaYukXqdUY5nuFKn/qiCon0O88g==",
+ "license": "Apache 2.0",
+ "dependencies": {
+ "@grpc/grpc-js": "^1.11.1",
+ "@grpc/proto-loader": "^0.7.7",
+ "@octokit/request": "^8.1.1",
+ "@sweet-monads/either": "~3.2.0",
+ "@sweet-monads/maybe": "~3.2.0",
+ "@types/line-column": "^1.0.0",
+ "@types/lodash.clonedeep": "4.5.0",
+ "@types/seedrandom": "^3.0.4",
+ "adm-zip": "^0.5.16",
+ "antlr4ts": "^0.5.0-alpha.4",
+ "chalk": "^4.1.2",
+ "cli-progress": "^3.12.0",
+ "cross-spawn": "^7.0.6",
+ "eol": "^0.9.1",
+ "immutable": "^4.3.0",
+ "json-bigint": "^1.0.0",
+ "line-column": "^1.0.2",
+ "lodash": "^4.17.21",
+ "lodash.clonedeep": "4.5.0",
+ "seedrandom": "^3.0.5",
+ "tar": "^7.5.13",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "quint": "dist/src/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@js-sdsl/ordered-map": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
+ "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
+ "node_modules/@octokit/endpoint": {
+ "version": "9.0.6",
+ "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
+ "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/types": "^13.1.0",
+ "universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@octokit/openapi-types": {
+ "version": "24.2.0",
+ "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
+ "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
+ "license": "MIT"
+ },
+ "node_modules/@octokit/request": {
+ "version": "8.4.1",
+ "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz",
+ "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/endpoint": "^9.0.6",
+ "@octokit/request-error": "^5.1.1",
+ "@octokit/types": "^13.1.0",
+ "universal-user-agent": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@octokit/request-error": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
+ "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/types": "^13.1.0",
+ "deprecation": "^2.0.0",
+ "once": "^1.4.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@octokit/types": {
+ "version": "13.10.0",
+ "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
+ "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
+ "license": "MIT",
+ "dependencies": {
+ "@octokit/openapi-types": "^24.2.0"
+ }
+ },
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
+ "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
+ "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
+ "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz",
+ "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@sweet-monads/either": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@sweet-monads/either/-/either-3.2.0.tgz",
+ "integrity": "sha512-n+nR0b60GRTKb+D76qhTf4NEBXU9zfpigYYEtKtSYbV+5+i5gxr9jFd64pYkY2O7hVsb/G7nspbAeFni/i1ltA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sweet-monads/interfaces": "^3.2.0"
+ }
+ },
+ "node_modules/@sweet-monads/interfaces": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@sweet-monads/interfaces/-/interfaces-3.3.0.tgz",
+ "integrity": "sha512-66akGvjPD4lizQy+w4JSltJilc2w/QPdw8lPAniLJGHwyjmrw9xMJLx76Q/GDnbCU59Werses4aZJLWOlJrL5A==",
+ "license": "MIT"
+ },
+ "node_modules/@sweet-monads/maybe": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@sweet-monads/maybe/-/maybe-3.2.0.tgz",
+ "integrity": "sha512-/t+K0D/kBfkYOkZaePEsrK868at0M9UIEVgehcM0xscrCSZhKWGteE41vl2XJQqh8WyiFo/mZ5y7eAPSYzS+pg==",
+ "license": "MIT",
+ "dependencies": {
+ "@sweet-monads/interfaces": "^3.2.0"
+ }
+ },
+ "node_modules/@types/line-column": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@types/line-column/-/line-column-1.0.2.tgz",
+ "integrity": "sha512-099oFQmp/Tlf20xW5XI5R4F69N6lF/zQ09XDzc3R5BOLFlqIotgKoNIyj0HD4fQLWcGDreDJv8k/BkLJscrDrw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.17.25",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz",
+ "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha512-IHijjFVPJTvzvrNPz+6nQy5lZQb7uh807RfTIEaQBrZXrIGjZy0L2dEb3hju34J0eqbXLCY6Hub/g81Jl4pGCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "26.6.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.4.tgz",
+ "integrity": "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~8.9.0"
+ }
+ },
+ "node_modules/@types/seedrandom": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.8.tgz",
+ "integrity": "sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==",
+ "license": "MIT"
+ },
+ "node_modules/adm-zip": {
+ "version": "0.5.18",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.18.tgz",
+ "integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/antlr4ts": {
+ "version": "0.5.0-alpha.4",
+ "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz",
+ "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/bignumber.js": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cli-progress": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz",
+ "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "string-width": "^4.2.3"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/deprecation": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
+ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/eol": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz",
+ "integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==",
+ "license": "MIT"
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/immutable": {
+ "version": "4.3.9",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.9.tgz",
+ "integrity": "sha512-ObHy4YN7ycwZOUCLI1/6svfyAFu7vL8RhAvVu/bh/RZW9EPlOyDaQ9jDQWCtdqzaXUjgXZCW1migtHE7YI7UGQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==",
+ "license": "MIT",
+ "dependencies": {
+ "isarray": "1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/json-bigint": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bignumber.js": "^9.0.0"
+ }
+ },
+ "node_modules/line-column": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/line-column/-/line-column-1.0.2.tgz",
+ "integrity": "sha512-Ktrjk5noGYlHsVnYWh62FLVs4hTb8A3e+vucNZMgPeAOITdshMSgv4cCZQeRDjm7+goqmo6+liZwTXo+U3sVww==",
+ "license": "MIT",
+ "dependencies": {
+ "isarray": "^1.0.0",
+ "isobject": "^2.0.0"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
+ "license": "MIT"
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
+ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/protobufjs": {
+ "version": "7.6.6",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz",
+ "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.5",
+ "@protobufjs/eventemitter": "^1.1.1",
+ "@protobufjs/fetch": "^1.1.1",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.1",
+ "@types/node": ">=13.7.0",
+ "long": "^5.3.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/seedrandom": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tar": {
+ "version": "7.5.22",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz",
+ "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.1.0",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
+ "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
+ "license": "MIT"
+ },
+ "node_modules/universal-user-agent": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
+ "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
+ "license": "ISC"
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.3",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz",
+ "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==",
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ }
+ }
+}
diff --git a/proof/quint/package.json b/proof/quint/package.json
new file mode 100644
index 0000000..262330b
--- /dev/null
+++ b/proof/quint/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "lambda-aitia-quint-checks",
+ "version": "0.0.0",
+ "private": true,
+ "license": "Apache-2.0 AND LGPL-2.1-or-later",
+ "dependencies": {
+ "@informalsystems/quint": "0.33.0"
+ }
+}
diff --git a/proof/quint/process_runner.py b/proof/quint/process_runner.py
new file mode 100644
index 0000000..f9fda54
--- /dev/null
+++ b/proof/quint/process_runner.py
@@ -0,0 +1,68 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+"""Run a model checker with real output and a bounded silence watchdog."""
+
+from __future__ import annotations
+
+import os
+import selectors
+import signal
+import subprocess
+import time
+from pathlib import Path
+from typing import Sequence
+
+
+PROGRESS = (
+ "Compiling to TLA+", "Starting checker server", "SanyParser",
+ "Running TLC", "Computing initial states", "states generated",
+ "Model checking completed", "No violation found", "Invariant q_inv is violated",
+ "found a counterexample", "Checking temporal properties", "Temporal property",
+)
+
+
+def run(command: Sequence[str], cwd: Path, label: str, *, timeout: float = 60,
+ silence: float = 12) -> tuple[int, str]:
+ """Return status and exact output; never count a silent checker as progress."""
+ process = subprocess.Popen(command, cwd=cwd, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT, start_new_session=True)
+ assert process.stdout is not None
+ selector = selectors.DefaultSelector()
+ selector.register(process.stdout, selectors.EVENT_READ)
+ chunks: list[bytes] = []
+ pending = b""
+ began = last_output = time.monotonic()
+ try:
+ while selector.get_map():
+ now = time.monotonic()
+ if now - began > timeout or now - last_output > silence:
+ reason = "total timeout" if now - began > timeout else "silent checker"
+ os.killpg(process.pid, signal.SIGTERM)
+ try:
+ process.wait(timeout=3)
+ except subprocess.TimeoutExpired:
+ os.killpg(process.pid, signal.SIGKILL)
+ process.wait()
+ partial = b"".join(chunks).decode(errors="replace")
+ raise TimeoutError(f"{label}: {reason} after {now - last_output:.1f}s without output\n{partial[-4000:]}")
+ events = selector.select(timeout=min(1.0, silence))
+ for key, _ in events:
+ data = os.read(key.fd, 65536)
+ if not data:
+ selector.unregister(key.fileobj)
+ continue
+ last_output = time.monotonic()
+ chunks.append(data)
+ pending += data
+ while b"\n" in pending:
+ line, pending = pending.split(b"\n", 1)
+ decoded = line.decode(errors="replace")
+ if any(marker in decoded for marker in PROGRESS):
+ print(f"{label}: {decoded.strip()}", flush=True)
+ if pending and any(marker in pending.decode(errors="replace") for marker in PROGRESS):
+ print(f"{label}: {pending.decode(errors='replace').strip()}", flush=True)
+ return process.wait(timeout=3), b"".join(chunks).decode(errors="replace")
+ finally:
+ selector.close()
+ process.stdout.close()
diff --git a/proof/quint/test_lifecycle_replay.py b/proof/quint/test_lifecycle_replay.py
new file mode 100644
index 0000000..49e2ccd
--- /dev/null
+++ b/proof/quint/test_lifecycle_replay.py
@@ -0,0 +1,79 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+"""Fail-closed controls for the finite lifecycle graph admission gate."""
+from pathlib import Path
+import tempfile
+import unittest
+from unittest.mock import patch
+
+import check_lifecycle_replay as checker
+
+
+class LifecycleReplayTests(unittest.TestCase):
+ def graph(self, suffix='', omitted=None):
+ values = {field: False if field.endswith('Safe') or field in
+ ('adapterInstalled', 'reportedSuccess', 'effectStarted') else 0
+ for field in checker.FIELDS if field != omitted}
+ label = ', '.join(f'{key} |-> {str(value).upper() if isinstance(value, bool) else value}'
+ for key, value in values.items())
+ return f'"1" [label="q = [{label}]", style = filled];\n"1" -> "1" [label="Terminal"];\n' + suffix
+
+ def read(self, source, expected=1):
+ with tempfile.TemporaryDirectory() as directory:
+ path = Path(directory) / 'graph.dot'
+ path.write_text(source)
+ return checker.read_graph(path, expected)
+
+ def test_complete_schema(self):
+ nodes, edges, initial = self.read(self.graph())
+ self.assertEqual(set(nodes[initial]), set(checker.FIELDS))
+ self.assertEqual(edges, [('1', '1', 'Terminal')])
+
+ def test_missing_field_is_not_partial_correspondence(self):
+ with self.assertRaisesRegex(ValueError, 'schema'):
+ self.read(self.graph(omitted='deliveryCalls'))
+
+ def test_missing_node_fails(self):
+ with self.assertRaisesRegex(ValueError, 'missing state'):
+ self.read(self.graph('"1" -> "2" [label="Verify"];'))
+
+ def test_unknown_action_is_not_scheme_code(self):
+ with self.assertRaisesRegex(ValueError, 'unknown TLC action'):
+ self.read(self.graph('"1" -> "1" [label="Unexpected"];'))
+
+ def test_incomplete_tlc_count_fails(self):
+ with self.assertRaisesRegex(ValueError, 'incomplete graph'):
+ self.read(self.graph(), expected=2)
+
+ def test_unreachable_state_fails(self):
+ with patch.object(checker, 'ACTIONS', ('Terminal',)):
+ with self.assertRaisesRegex(ValueError, 'unreachable'):
+ checker.covering_traces({'1': {'clock': 0}, '2': {'clock': 1}},
+ [('1', '1', 'Terminal')], '1')
+
+ def test_covering_paths_cover_all_states_and_self_loop_action(self):
+ with patch.object(checker, 'ACTIONS', ('Verify', 'Terminal')):
+ traces = checker.covering_traces({'1': {'clock': 0}, '2': {'clock': 1}},
+ [('1', '2', 'Verify'), ('2', '2', 'Terminal')], '1')
+ self.assertEqual({state['clock'] for trace in traces for _, state in trace}, {0, 1})
+ self.assertEqual({action for trace in traces for action, _ in trace}, {'init', 'Verify', 'Terminal'})
+
+ def test_non_stuttering_cycle_fails_closed(self):
+ with self.assertRaisesRegex(ValueError, 'not a DAG'):
+ checker.covering_traces({'1': {'clock': 0}, '2': {'clock': 1}},
+ [('1', '2', 'Verify'), ('2', '1', 'Revoke')], '1')
+
+ def test_native_batches_preserve_every_trace_and_bound_actual_steps(self):
+ traces = [[('init', {})] * size for size in (80, 80, 21, 140, 20)]
+ packed = list(checker.batches(traces))
+ self.assertEqual([trace for batch in packed for trace in batch], traces)
+ self.assertTrue(all(sum(map(len, batch)) <= 160 for batch in packed))
+ self.assertEqual([sum(map(len, batch)) for batch in packed], [160, 21, 160])
+
+ def test_single_oversized_trace_is_not_split_or_given_a_larger_timeout(self):
+ with self.assertRaisesRegex(ValueError, 'one trace exceeds'):
+ list(checker.batches([[('init', {})] * 161]))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/proof/quint/tlc-config.json b/proof/quint/tlc-config.json
new file mode 100644
index 0000000..e55923a
--- /dev/null
+++ b/proof/quint/tlc-config.json
@@ -0,0 +1 @@
+{"workers":"1","maxHeap":"-Xmx512m"}
diff --git a/schemas/vocabulary-manifest.json b/schemas/vocabulary-manifest.json
index 0a244ad..b0bc9dd 100644
--- a/schemas/vocabulary-manifest.json
+++ b/schemas/vocabulary-manifest.json
@@ -1,6 +1,6 @@
{
"manifestSchema": "lambda-aitia.vocabulary-manifest",
- "revision": 2,
+ "revision": 5,
"semanticOwner": "lambda-aitia",
"nodeKinds": [
"artifact",
@@ -33,7 +33,7 @@
"observation": ["subject", "source", "clock-role", "logical-position"],
"event": ["subject", "event-kind", "observation", "payload-digest", "modality", "commitment-state", "causal-parents"],
"action": ["subject", "action-kind", "requested-by", "scope"],
- "obligation": ["subject", "claim", "snapshot", "evidence-kind", "capability", "scope"],
+ "obligation": ["subject", "claim", "snapshot", "snapshot-revision", "snapshot-context-digest", "evidence-kind", "capability", "scope"],
"evidence": ["producer", "tool", "tool-version", "input-artifacts", "obligation", "subject", "scope", "valid-from", "valid-until", "admission-state"],
"counterexample": ["subject", "challenges", "evidence", "details-digest"],
"finding": ["subject", "challenges", "evidence", "finding-kind"],
@@ -56,7 +56,7 @@
},
"relationPlanes": {
"provenance": ["derived-from", "generated-by", "cites", "used"],
- "structural": ["depends-on", "implements", "tests", "refines"],
+ "structural": ["depends-on", "composes", "implements", "tests", "refines"],
"causal": ["precedes", "causal-parent", "enables", "prevents"],
"assurance": ["supports", "discharges", "defeats", "invalidates"],
"authority": ["authorizes", "denies", "requires-review"]
@@ -66,6 +66,11 @@
"lambda-aitia.assurance-snapshot",
"lambda-aitia.invalidation-receipt",
"lambda-aitia.planning-result",
+ "lambda-aitia.replacement-requirements",
+ "lambda-aitia.replacement-declaration",
+ "lambda-aitia.verifier-choices",
+ "lambda-aitia.evidence-admission",
+ "lambda-aitia.composition-requirements",
"lambda-aitia.native-descriptor",
"lambda-aitia.native-error"
]
diff --git a/scripts/tests/test_build_native_library.py b/scripts/tests/test_build_native_library.py
index f3b0fe0..7a067d0 100644
--- a/scripts/tests/test_build_native_library.py
+++ b/scripts/tests/test_build_native_library.py
@@ -28,6 +28,25 @@ def build_module():
class NativeLinkPolicyTest(unittest.TestCase):
+ def test_orgize_header_search_path_names_the_include_directory(self) -> None:
+ module = build_module()
+ with tempfile.TemporaryDirectory() as directory:
+ header_root = (
+ Path(directory)
+ / "pkg/github.com/tao3k/orgize/bindings/c/include"
+ )
+ header_root.mkdir(parents=True)
+ (header_root / "orgize.h").touch()
+ with patch.dict(
+ os.environ,
+ {"GERBIL_PATH": directory, "C_INCLUDE_PATH": "/caller/include"},
+ ):
+ module.configure_orgize_header()
+ self.assertEqual(
+ os.environ["C_INCLUDE_PATH"],
+ f"{header_root.resolve()}:/caller/include",
+ )
+
def test_gambit_compiler_uses_the_gerbil_release_not_path(self) -> None:
module = build_module()
with tempfile.TemporaryDirectory() as directory:
diff --git a/scripts/tests/test_quint_replay.py b/scripts/tests/test_quint_replay.py
new file mode 100644
index 0000000..bf13b03
--- /dev/null
+++ b/scripts/tests/test_quint_replay.py
@@ -0,0 +1,74 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+"""The correspondence runner must fail closed on schema and checker failures."""
+
+import importlib.util
+from pathlib import Path
+import sys
+import tempfile
+import unittest
+from unittest.mock import patch
+
+ROOT = Path(__file__).resolve().parents[2] / "proof/quint"
+sys.path.insert(0, str(ROOT))
+SPEC = importlib.util.spec_from_file_location("quint_replay", ROOT / "check_scheme_replay.py")
+replay = importlib.util.module_from_spec(SPEC)
+SPEC.loader.exec_module(replay)
+
+
+def initial():
+ return {"mbt::actionTaken": "init", "q": {
+ "work": {"active": True, "queued": True, "cancelRequested": False},
+ "known": False, "reviewed": False, "retired": False, "lateFound": False,
+ "gapFound": False, "ingressOpen": True, "epoch": {"#bigint": "0"},
+ "grantEpoch": {"#bigint": "0"}, "pending": False, "attempted": False,
+ "rejectedAfterRetirement": False, "startedAfterRetirement": False}}
+
+
+class QuintReplayTest(unittest.TestCase):
+ def test_all_projected_slots_and_itf_integer_are_data(self):
+ row = replay.state_row(initial())
+ self.assertIn("(authority-epoch 0)", row)
+ self.assertIn("(cancellation-requested? #f)", row)
+ self.assertEqual(row.count("?") + row.count("-epoch"), len(replay.FIELDS))
+
+ def test_added_model_field_cannot_be_silently_ignored(self):
+ state = initial()
+ state["q"]["newAuthority"] = True
+ with self.assertRaisesRegex(ValueError, "schema changed"):
+ replay.state_row(state)
+
+ def test_missing_work_field_cannot_be_silently_ignored(self):
+ state = initial()
+ del state["q"]["work"]["queued"]
+ with self.assertRaisesRegex(ValueError, "schema changed"):
+ replay.state_row(state)
+
+ def test_action_text_cannot_be_executed(self):
+ state = initial()
+ state["mbt::actionTaken"] = "init) (error injected) ("
+ with self.assertRaisesRegex(ValueError, "unknown Quint action"):
+ replay.state_row(state)
+
+ def test_non_scalar_itf_value_is_rejected(self):
+ with self.assertRaises(ValueError):
+ replay.scalar({"#set": []})
+
+ def test_tool_failure_is_not_a_negative_control(self):
+ with tempfile.TemporaryDirectory() as directory, patch.object(replay, "run", return_value=(2, "Invariant violated")):
+ with self.assertRaisesRegex(RuntimeError, "unexpected Quint result"):
+ replay.generate("quint", Path(directory), "fault", "unfencedStep", "RetiredIsFenced", 1)
+
+ def test_success_without_completed_output_is_rejected(self):
+ with tempfile.TemporaryDirectory() as directory, patch.object(replay, "run", return_value=(0, "[ok] No violation found")):
+ with self.assertRaisesRegex(RuntimeError, "incomplete trace output"):
+ replay.generate("quint", Path(directory), "safe", "step", "TypeOK", 1)
+
+ def test_success_exit_without_success_marker_is_rejected(self):
+ with tempfile.TemporaryDirectory() as directory, patch.object(replay, "run", return_value=(0, "tool error")):
+ with self.assertRaisesRegex(RuntimeError, "unexpected Quint result"):
+ replay.generate("quint", Path(directory), "safe", "step", "TypeOK", 1)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/t/ADR/unit/ADR-module-test.ss b/t/ADR/unit/ADR-module-test.ss
index 272da63..f2effce 100644
--- a/t/ADR/unit/ADR-module-test.ss
+++ b/t/ADR/unit/ADR-module-test.ss
@@ -6,7 +6,7 @@
(import :std/test
(only-in :std/error Error?)
(only-in :clan/poo/object .ref)
- :poo-flow/src/module-system/declaration/interface
+ :poo-flow/src/user-interface/module-selection
(only-in :poo-flow/lambda-aitia/modules/assurance/interface
assurance-decision?)
:poo-flow/lambda-aitia/modules/ADR/interface)
diff --git a/t/assurance/unit/assurance-kernel-test.ss b/t/assurance/unit/assurance-kernel-test.ss
index 38f2ea4..7adeeb8 100644
--- a/t/assurance/unit/assurance-kernel-test.ss
+++ b/t/assurance/unit/assurance-kernel-test.ss
@@ -5,7 +5,10 @@
(import :std/test
(only-in :std/error Error?)
+ (only-in :std/list/list find)
(only-in :clan/poo/object .o .ref)
+ (only-in :poo-flow/core/contribution/verification
+ poo-flow-verification-adapter poo-flow-verify)
:poo-flow/lambda-aitia/modules/assurance/interface)
(export assurance-kernel-test)
@@ -64,17 +67,102 @@
(assurance-relation "relation/reverse-dependency" 'structural 'implements
"artifact/source" "claim/release" 'declared))
-(def (software-snapshot nodes relations (unresolved '()))
- (assurance-snapshot
- "snapshot/software" "r1" "graph/software"
- '(("artifact/source" . "r1")) '(("claim/release" . "r1"))
- "event-cut/1" "policy/release" "r1" nodes relations
- evidence-identities: '("evidence/test") unresolved: unresolved))
+(def (verification-obligation id capability)
+ (assurance-obligation
+ id "r1" 'unknown digest-a
+ subject: "software/release" claim: "claim/release"
+ snapshot: "snapshot/software" evidence-kind: 'native-test
+ capability: capability scope: "repository"))
+(def (verification-support id)
+ (assurance-relation
+ (string-append "relation/support/" id) 'assurance 'supports
+ id "claim/release" 'declared))
+(def (verification-dependency source target)
+ (assurance-relation
+ (string-append "relation/depends/" source "/" target)
+ 'structural 'depends-on source target 'declared))
+(def (verification-snapshot obligations relations)
+ (software-snapshot
+ (append (list artifact claim evidence) obligations)
+ (append (list dependency) relations)))
+
+(def (software-snapshot nodes relations (unresolved '()) (revision "r1"))
+ (def (construct values)
+ (assurance-snapshot
+ "snapshot/software" revision "graph/software"
+ '(("artifact/source" . "r1")) '(("claim/release" . "r1"))
+ "event-cut/1" "policy/release" "r1" values relations
+ evidence-identities: '("evidence/test") unresolved: unresolved))
+ (let* ((candidate (construct nodes))
+ (bound-nodes
+ (map (lambda (node)
+ (if (and (assurance-obligation? node)
+ (string=? (.ref node 'snapshot)
+ (.ref candidate 'identity))
+ (not (.ref node 'snapshot-revision))
+ (not (.ref node 'snapshot-context-digest)))
+ (assurance-bind-obligation-to-snapshot node candidate)
+ node))
+ nodes)))
+ (construct bound-nodes)))
(def full-software-snapshot
(software-snapshot
(list artifact claim obligation evidence decision effect)
(list dependency discharge obligation-support
decision-dependency authorization)))
+(def current-obligation
+ (find (lambda (node)
+ (and (assurance-obligation? node)
+ (equal? (.ref node 'identity) "obligation/verify")))
+ (.ref full-software-snapshot 'nodes)))
+(def support-outcome
+ (assurance-verifier-outcome
+ "outcome/kernel" 'succeeded
+ producer: "gxtest" tool: "gerbil-test" tool-version: "v19"
+ obligation: "obligation/verify" subject: "software/release"
+ scope: "repository"
+ snapshot-revision: (.ref full-software-snapshot 'revision)
+ snapshot-context-digest: (.ref full-software-snapshot 'context-digest)
+ inputs: (list (assurance-verifier-input artifact))
+ output-digest: digest-b))
+(def support-adapter
+ (poo-flow-verification-adapter
+ "test/kernel-support" (lambda (subject-value now-value until-value) #t)
+ assurance-verification-subject-snapshot))
+(def support-receipt
+ (poo-flow-verify
+ support-adapter
+ (assurance-verification-subject
+ full-software-snapshot current-obligation evidence support-outcome)
+ 10 20))
+(def (kernel-support? relation-value evidence-value)
+ (assurance-support-sealed-under-adapter?
+ relation-value evidence-value current-obligation full-software-snapshot
+ support-outcome support-adapter support-receipt 11))
+
+(def replacement-claim
+ (assurance-claim
+ "claim/release" "r2" 'unknown digest-b
+ subject: "software/release" predicate: "release-ready-v2"
+ assumptions: '("assumption/toolchain")
+ support-requirements: '("obligation/verify-next")
+ scope: "repository" valid-from: "2026-09-17"))
+(def replacement-candidate
+ (assurance-obligation
+ "obligation/verify-next" "r2" 'unknown digest-a
+ subject: "software/release" claim: "claim/release"
+ snapshot: "snapshot/software" evidence-kind: 'native-test
+ capability: 'gerbil-test scope: "repository"))
+(def replacement-support
+ (assurance-relation
+ "relation/replacement-support" 'assurance 'supports
+ "obligation/verify-next" "claim/release" 'declared))
+(def (replacement-draft nodes relations)
+ (assurance-snapshot
+ "snapshot/software" "r2" "graph/software"
+ '(("artifact/source" . "r1")) '(("claim/release" . "r2"))
+ "event-cut/1" "policy/release" "r1"
+ nodes relations))
(def assurance-kernel-test
(test-suite "Aitia POO-native assurance kernel"
@@ -102,6 +190,12 @@
(test-case "relation kinds cannot cross their owned plane"
(check-equal? (assurance-relation? dependency) #t)
+ (check-equal?
+ (assurance-relation?
+ (assurance-relation
+ "relation/composes" 'structural 'composes
+ "claim/composite" "claim/component-a" 'declared))
+ #t)
(check-exception
(assurance-relation "bad" 'causal 'depends-on
"claim/release" "artifact/source" 'declared)
@@ -235,35 +329,35 @@
(test-case "alternate evidence and temporal order grant no support"
(check-equal?
- (assurance-support-admissible? discharge evidence obligation) #t)
+ (kernel-support? discharge evidence) #t)
(check-equal?
- (assurance-support-admissible?
- discharge (.o (:: @ evidence) state: 'hypothesized) obligation)
+ (kernel-support?
+ discharge (.o (:: @ evidence) state: 'hypothesized))
#f)
(check-equal?
- (assurance-support-admissible?
+ (kernel-support?
(.o (:: @ discharge) modality: 'counterfactual)
- evidence obligation)
+ evidence)
#f)
(check-equal?
- (assurance-support-admissible?
+ (kernel-support?
(assurance-relation "relation/precedes" 'causal 'precedes
"evidence/test" "obligation/verify" 'observed)
- evidence obligation)
+ evidence)
#f))
(test-case "candidate or mismatched evidence cannot discharge an obligation"
(check-equal?
- (assurance-support-admissible?
- discharge (.o (:: @ evidence) admission-state: 'candidate) obligation)
+ (kernel-support?
+ discharge (.o (:: @ evidence) admission-state: 'candidate))
#f)
(check-equal?
- (assurance-support-admissible?
- discharge (.o (:: @ evidence) obligation: "obligation/other") obligation)
+ (kernel-support?
+ discharge (.o (:: @ evidence) obligation: "obligation/other"))
#f)
(check-equal?
- (assurance-support-admissible?
- discharge (.o (:: @ evidence) revision: "r2") obligation)
+ (kernel-support?
+ discharge (.o (:: @ evidence) revision: "r2"))
#f))
(test-case "change invalidation is deterministic and never grants authority"
@@ -359,6 +453,811 @@
(cons "artifact/source"
(assurance-invalidation-receipt-ref claim-change 'impacted)))))
+ (test-case "verification selection is pure and permutation-stable"
+ (let* ((policy
+ (assurance-verification-policy
+ "policy/release" "r1" '(lean gerbil-test gerbil-test)))
+ (permuted-policy
+ (assurance-verification-policy
+ "policy/release" "r1" '(gerbil-test lean)))
+ (permuted
+ (software-snapshot
+ (list effect decision evidence obligation claim artifact)
+ (list authorization decision-dependency obligation-support
+ discharge dependency)))
+ (left
+ (assurance-plan-verification
+ "plan/release" full-software-snapshot
+ '("artifact/source") policy))
+ (right
+ (assurance-plan-verification
+ "plan/release" permuted
+ '("artifact/source") permuted-policy)))
+ (check-equal? (assurance-verification-policy? policy) #t)
+ (check-equal? (.ref policy 'capabilities) '(gerbil-test lean))
+ (check-equal? (assurance-verification-plan? left) #t)
+ (check-equal? (.ref left 'schema) "lambda-aitia.planning-result")
+ (check-equal?
+ (.ref obligation 'snapshot-context-digest) #f)
+ (check-equal?
+ (.ref (car (filter assurance-obligation?
+ (.ref full-software-snapshot 'nodes)))
+ 'snapshot-context-digest)
+ (.ref full-software-snapshot 'context-digest))
+ (check-equal? (.ref left 'selected-obligations)
+ '("obligation/verify"))
+ (check-equal? (.ref left 'blocked-obligations) '())
+ (check-equal?
+ (assurance-verification-request? (car (.ref left 'requests))) #t)
+ (check-equal? (.ref (car (.ref left 'requests)) 'capability)
+ 'gerbil-test)
+ (check-equal? (.ref (car (.ref left 'requests)) 'selected?) #t)
+ (check-equal? (.ref left 'digest) (.ref right 'digest))
+ (check-equal? (.ref left 'verifier-executed?) #f)
+ (check-equal? (.ref left 'release-authorized?) #f)
+ (check-equal? (.ref left 'runtime-executed?) #f)
+ (check-equal? (.ref left 'blocked-effects) '("effect/deploy"))
+ (check-equal? (car (.ref left 'witnesses))
+ '("artifact/source" ("artifact/source") ()))
+ (check-equal?
+ (assurance-verification-request?
+ (.o (:: @ (car (.ref left 'requests)))
+ blocker: 'capability-unavailable))
+ #f)
+ (check-equal?
+ (assurance-verification-plan?
+ (.o (:: @ left) release-authorized?: #t))
+ #f)))
+
+ (test-case "replacement requirements never silently rebind an old obligation"
+ (let* ((target
+ (software-snapshot
+ (list artifact claim evidence) (list dependency) '() "r2"))
+ (permuted-source
+ (software-snapshot
+ (list effect decision evidence obligation claim artifact)
+ (list authorization decision-dependency obligation-support
+ discharge dependency)))
+ (left
+ (assurance-derive-replacement-requirements
+ "replacement/source" full-software-snapshot target
+ '("artifact/source")))
+ (right
+ (assurance-derive-replacement-requirements
+ "replacement/source" permuted-source target
+ '("artifact/source")))
+ (requirement (car (.ref left 'requirements)))
+ (unchanged
+ (assurance-derive-replacement-requirements
+ "replacement/unchanged" full-software-snapshot
+ full-software-snapshot '("artifact/source")))
+ (unknown-change
+ (assurance-derive-replacement-requirements
+ "replacement/unknown-change" full-software-snapshot
+ target '("artifact/source" "artifact/unknown")))
+ (unknown-only
+ (assurance-derive-replacement-requirements
+ "replacement/unknown-only" full-software-snapshot
+ target '("artifact/unknown")))
+ (stale-source
+ (software-snapshot
+ (list artifact claim evidence decision effect
+ (assurance-obligation
+ "obligation/verify" "r1" 'unknown digest-a
+ subject: "software/release" claim: "claim/release"
+ snapshot: "snapshot/software" evidence-kind: 'native-test
+ capability: 'gerbil-test scope: "repository"
+ snapshot-revision: "r0"
+ snapshot-context-digest: digest-a))
+ (list dependency discharge obligation-support
+ decision-dependency authorization)))
+ (stale
+ (assurance-derive-replacement-requirements
+ "replacement/stale-source" stale-source target
+ '("artifact/source"))))
+ (check-equal? (assurance-replacement-derivation? left) #t)
+ (check-equal? (.ref left 'digest) (.ref right 'digest))
+ (check-equal? (length (.ref left 'requirements)) 1)
+ (check-equal? (assurance-replacement-requirement? requirement) #t)
+ (check-equal? (.ref requirement 'source-obligation)
+ "obligation/verify")
+ (check-equal? (.ref requirement 'evidence-kind) 'native-test)
+ (check-equal? (.ref requirement 'blocker) #f)
+ (check-equal? (car (.ref left 'witnesses))
+ '("artifact/source" ("artifact/source") ()))
+ (check-equal? (.ref (car (.ref unchanged 'requirements)) 'blocker)
+ 'target-unchanged)
+ (check-equal?
+ (.ref (car (.ref unknown-change 'requirements)) 'blocker)
+ 'change-unresolved)
+ (check-equal? (.ref unknown-only 'requirements) '())
+ (check-equal? (.ref unknown-only 'unresolved)
+ '("artifact/unknown"))
+ (check-equal? (.ref (car (.ref stale 'requirements)) 'blocker)
+ 'stale-source)
+ (check-equal? (.ref left 'verifier-executed?) #f)
+ (check-equal? (.ref left 'release-authorized?) #f)
+ (check-equal?
+ (filter assurance-obligation? (.ref target 'nodes)) '())))
+
+ (test-case "revised claims and unknown targets block automatic replacement"
+ (let* ((revised-claim
+ (assurance-claim
+ "claim/release" "r2" 'unknown digest-a
+ subject: "software/release" predicate: "release-ready-v2"
+ assumptions: '("assumption/toolchain")
+ support-requirements: '("obligation/verify")
+ scope: "repository" valid-from: "2026-09-17"))
+ (changed-claim-target
+ (assurance-snapshot
+ "snapshot/software" "r2" "graph/software"
+ '(("artifact/source" . "r1")) '(("claim/release" . "r2"))
+ "event-cut/1" "policy/release" "r1"
+ (list artifact revised-claim)
+ (list dependency)))
+ (unknown-target
+ (software-snapshot
+ (list artifact claim evidence) (list dependency)
+ '("artifact/unresolved") "r2"))
+ (foreign-target
+ (assurance-snapshot
+ "snapshot/other" "r2" "graph/software"
+ '(("artifact/source" . "r1")) '(("claim/release" . "r1"))
+ "event-cut/1" "policy/release" "r1"
+ (list artifact claim) (list dependency)))
+ (changed-claim
+ (assurance-derive-replacement-requirements
+ "replacement/changed-claim" full-software-snapshot
+ changed-claim-target '("artifact/source")))
+ (unknown
+ (assurance-derive-replacement-requirements
+ "replacement/unknown" full-software-snapshot
+ unknown-target '("artifact/source")))
+ (foreign
+ (assurance-derive-replacement-requirements
+ "replacement/foreign" full-software-snapshot
+ foreign-target '("artifact/source"))))
+ (check-equal?
+ (.ref (car (.ref changed-claim 'requirements)) 'blocker)
+ 'claim-changed)
+ (check-equal?
+ (.ref (car (.ref unknown 'requirements)) 'blocker)
+ 'target-unresolved)
+ (check-equal?
+ (.ref (car (.ref foreign 'requirements)) 'blocker)
+ 'target-identity-changed)))
+
+ (test-case "an explicit fresh replacement binds without promoting old evidence"
+ (let* ((draft
+ (replacement-draft
+ (list artifact replacement-claim replacement-candidate)
+ (list dependency replacement-support)))
+ (permuted-draft
+ (replacement-draft
+ (list replacement-candidate artifact replacement-claim)
+ (list replacement-support dependency)))
+ (derivation
+ (assurance-derive-replacement-requirements
+ "replacement/explicit" full-software-snapshot draft
+ '("artifact/source")))
+ (permuted-derivation
+ (assurance-derive-replacement-requirements
+ "replacement/explicit" full-software-snapshot permuted-draft
+ '("artifact/source"))))
+ (check-equal? (.ref (car (.ref derivation 'requirements)) 'blocker)
+ 'claim-changed)
+ (let-values (((final receipt)
+ (assurance-declare-replacement-obligation
+ "replacement/declaration" full-software-snapshot
+ draft derivation "obligation/verify"
+ "obligation/verify-next"))
+ ((permuted-final permuted-receipt)
+ (assurance-declare-replacement-obligation
+ "replacement/declaration" full-software-snapshot
+ permuted-draft permuted-derivation
+ "obligation/verify" "obligation/verify-next")))
+ (let ((bound
+ (find (lambda (node)
+ (equal? (.ref node 'identity)
+ "obligation/verify-next"))
+ (.ref final 'nodes))))
+ (check-equal? (assurance-replacement-declaration-receipt? receipt)
+ #t)
+ (check-equal? (.ref receipt 'blocker) #f)
+ (check-equal? (.ref bound 'snapshot-revision) "r2")
+ (check-equal? (.ref bound 'snapshot-context-digest)
+ (.ref final 'context-digest))
+ (check-equal? (.ref replacement-candidate 'snapshot-revision) #f)
+ (check-equal? (.ref receipt 'final-snapshot-digest)
+ (.ref final 'digest))
+ (check-equal? (.ref receipt 'verifier-executed?) #f)
+ (check-equal? (.ref receipt 'release-authorized?) #f)
+ (check-equal? (.ref final 'digest) (.ref permuted-final 'digest))
+ (check-equal? (.ref receipt 'digest)
+ (.ref permuted-receipt 'digest))
+ (check-equal?
+ (assurance-replacement-declaration-receipt?
+ (.o (:: @ receipt) release-authorized?: #t))
+ #f)))))
+
+ (test-case "replacement declaration rejects missing and hypothetical links"
+ (let* ((draft
+ (replacement-draft
+ (list artifact replacement-claim replacement-candidate)
+ (list dependency replacement-support)))
+ (derivation
+ (assurance-derive-replacement-requirements
+ "replacement/negative" full-software-snapshot draft
+ '("artifact/source")))
+ (missing-link
+ (replacement-draft
+ (list artifact replacement-claim replacement-candidate)
+ (list dependency)))
+ (hypothetical-link
+ (replacement-draft
+ (list artifact replacement-claim replacement-candidate)
+ (list dependency
+ (.o (:: @ replacement-support)
+ modality: 'hypothesized))))
+ (foreign-claim
+ (replacement-draft
+ (list artifact replacement-claim
+ (.o (:: @ replacement-candidate)
+ claim: "claim/foreign"))
+ (list dependency replacement-support)))
+ (forged-draft (.o (:: @ draft) digest: digest-a)))
+ (let-values (((missing-final missing-receipt)
+ (assurance-declare-replacement-obligation
+ "replacement/missing" full-software-snapshot
+ missing-link
+ (assurance-derive-replacement-requirements
+ "replacement/missing-derivation"
+ full-software-snapshot missing-link
+ '("artifact/source"))
+ "obligation/verify" "obligation/verify-next"))
+ ((hypothetical-final hypothetical-receipt)
+ (assurance-declare-replacement-obligation
+ "replacement/hypothetical" full-software-snapshot
+ hypothetical-link
+ (assurance-derive-replacement-requirements
+ "replacement/hypothetical-derivation"
+ full-software-snapshot hypothetical-link
+ '("artifact/source"))
+ "obligation/verify" "obligation/verify-next"))
+ ((stale-final stale-receipt)
+ (assurance-declare-replacement-obligation
+ "replacement/stale" full-software-snapshot
+ hypothetical-link derivation
+ "obligation/verify" "obligation/verify-next"))
+ ((foreign-final foreign-receipt)
+ (assurance-declare-replacement-obligation
+ "replacement/foreign-claim" full-software-snapshot
+ foreign-claim
+ (assurance-derive-replacement-requirements
+ "replacement/foreign-claim-derivation"
+ full-software-snapshot foreign-claim
+ '("artifact/source"))
+ "obligation/verify" "obligation/verify-next"))
+ ((forged-final forged-receipt)
+ (assurance-declare-replacement-obligation
+ "replacement/forged" full-software-snapshot
+ forged-draft
+ (assurance-derive-replacement-requirements
+ "replacement/forged-derivation"
+ full-software-snapshot forged-draft
+ '("artifact/source"))
+ "obligation/verify" "obligation/verify-next")))
+ (check-equal? missing-final #f)
+ (check-equal? (.ref missing-receipt 'blocker) 'support-link-missing)
+ (check-equal? hypothetical-final #f)
+ (check-equal? (.ref hypothetical-receipt 'blocker)
+ 'support-link-hypothetical)
+ (check-equal? stale-final #f)
+ (check-equal? (.ref stale-receipt 'blocker)
+ 'derivation-mismatch)
+ (check-equal? foreign-final #f)
+ (check-equal? (.ref foreign-receipt 'blocker)
+ 'candidate-claim-mismatch)
+ (check-equal? forged-final #f)
+ (check-equal? (.ref forged-receipt 'blocker)
+ 'derivation-mismatch))))
+
+ (test-case "verification obligations have a closed evidence-kind vocabulary"
+ (check-equal?
+ +assurance-evidence-kinds+
+ '(proof model-check replay unit-test integration-test native-test
+ static-analysis authorization-differential human-review))
+ (for-each
+ (lambda (kind)
+ (check-equal?
+ (assurance-obligation?
+ (assurance-obligation
+ "obligation/kind" "r1" 'unknown digest-a
+ subject: "software/release" claim: "claim/release"
+ snapshot: "snapshot/software" evidence-kind: kind
+ capability: 'verifier scope: "repository"))
+ #t))
+ +assurance-evidence-kinds+)
+ (check-exception
+ (assurance-obligation
+ "obligation/invalid-kind" "r1" 'unknown digest-a
+ subject: "software/release" claim: "claim/release"
+ snapshot: "snapshot/software" evidence-kind: 'arbitrary-pass
+ capability: 'verifier scope: "repository")
+ Error?))
+
+ (test-case "verifier choices explain matching and rejected declarations"
+ (let* ((policy
+ (assurance-verification-policy
+ "policy/release" "r1" '(gerbil-test)))
+ (plan
+ (assurance-plan-verification
+ "plan/verifier-choice" full-software-snapshot
+ '("artifact/source") policy))
+ (kind-mismatch
+ (assurance-verifier-candidate
+ "candidate/a" "r1" 0 '(proof) 'gerbil-test))
+ (capability-mismatch
+ (assurance-verifier-candidate
+ "candidate/b" "r1" 1 '(native-test) 'lean))
+ (chosen
+ (assurance-verifier-candidate
+ "candidate/z" "r1" 2 '(native-test unit-test) 'gerbil-test))
+ (alternative
+ (assurance-verifier-candidate
+ "candidate/c" "r1" 3 '(native-test) 'gerbil-test))
+ (catalog
+ (list alternative kind-mismatch chosen capability-mismatch))
+ (left
+ (assurance-explain-verifier-choices
+ "choices/release" plan catalog))
+ (right
+ (assurance-explain-verifier-choices
+ "choices/release" plan (reverse catalog)))
+ (revised
+ (assurance-explain-verifier-choices
+ "choices/release" plan
+ (list alternative kind-mismatch capability-mismatch
+ (assurance-verifier-candidate
+ "candidate/z" "r2" 2 '(native-test unit-test)
+ 'gerbil-test))))
+ (missing
+ (assurance-explain-verifier-choices
+ "choices/missing" plan (list kind-mismatch)))
+ (blocked-plan
+ (assurance-plan-verification
+ "plan/no-verifier" full-software-snapshot
+ '("artifact/source")
+ (assurance-verification-policy
+ "policy/release" "r1" '())))
+ (blocked
+ (assurance-explain-verifier-choices
+ "choices/blocked" blocked-plan (list chosen))))
+ (check-equal? (assurance-verifier-choice-receipt? left) #t)
+ (check-equal? (map (lambda (candidate) (.ref candidate 'identity))
+ (.ref left 'candidates))
+ '("candidate/a" "candidate/b" "candidate/z"
+ "candidate/c"))
+ (check-equal? (.ref left 'digest) (.ref right 'digest))
+ (check-equal? (equal? (.ref left 'digest) (.ref revised 'digest))
+ #f)
+ (check-equal? (map (lambda (choice) (.ref choice 'reason))
+ (.ref left 'choices))
+ '(evidence-kind-unsupported capability-mismatch
+ chosen lower-priority))
+ (check-equal? (map (lambda (choice) (.ref choice 'selected?))
+ (.ref left 'choices))
+ '(#f #f #t #f))
+ (check-equal? (.ref left 'unassigned-obligations) '())
+ (check-equal? (.ref missing 'unassigned-obligations)
+ '("obligation/verify"))
+ (check-equal? (.ref (car (.ref blocked 'choices)) 'reason)
+ 'request-blocked)
+ (check-equal? (.ref blocked 'unassigned-obligations) '())
+ (check-equal? (.ref left 'verifier-executed?) #f)
+ (check-equal? (.ref left 'release-authorized?) #f)
+ (check-exception
+ (assurance-explain-verifier-choices
+ "choices/duplicate" plan (list chosen chosen))
+ Error?)
+ (check-exception
+ (assurance-verifier-candidate
+ "candidate/unknown" "r1" 0 '(arbitrary-pass) 'gerbil-test)
+ Error?)))
+
+ (test-case "component support never supplies a composition obligation"
+ (let* ((component-a
+ (assurance-claim
+ "claim/component-a" "r1" 'supported digest-a
+ subject: "software/component-a" predicate: "ready"
+ scope: "repository" valid-from: "2026-09-17"))
+ (component-b
+ (assurance-claim
+ "claim/component-b" "r1" 'supported digest-a
+ subject: "software/component-b" predicate: "ready"
+ scope: "repository" valid-from: "2026-09-17"))
+ (composite
+ (assurance-claim
+ "claim/composite" "r1" 'unknown digest-b
+ subject: "software/composite" predicate: "composition-safe"
+ support-requirements: '("obligation/composition")
+ scope: "repository" valid-from: "2026-09-17"))
+ (candidate-obligation
+ (assurance-obligation
+ "obligation/composition" "r1" 'unknown digest-a
+ subject: "software/composite" claim: "claim/composite"
+ snapshot: "snapshot/composition"
+ evidence-kind: 'integration-test
+ capability: 'composition-test scope: "repository"))
+ (components
+ (list (assurance-relation
+ "relation/composes/a" 'structural 'composes
+ "claim/composite" "claim/component-a" 'declared)
+ (assurance-relation
+ "relation/composes/b" 'structural 'composes
+ "claim/composite" "claim/component-b" 'declared)))
+ (direct-support
+ (assurance-relation
+ "relation/support/composition" 'assurance 'supports
+ "obligation/composition" "claim/composite" 'declared)))
+ (def (construct nodes relations)
+ (assurance-snapshot
+ "snapshot/composition" "r1" "graph/composition"
+ '() '(("claim/component-a" . "r1")
+ ("claim/component-b" . "r1")
+ ("claim/composite" . "r1"))
+ "event-cut/1" "policy/composition" "r1"
+ nodes relations))
+ (let* ((missing-snapshot
+ (construct (list component-a component-b composite)
+ components))
+ (missing
+ (assurance-derive-composition-requirements
+ "composition/missing" missing-snapshot))
+ (permuted
+ (assurance-derive-composition-requirements
+ "composition/missing"
+ (construct (list composite component-b component-a)
+ (reverse components))))
+ (candidate
+ (construct
+ (list component-a component-b composite candidate-obligation)
+ (cons direct-support components)))
+ (stale-obligation
+ (assurance-obligation
+ "obligation/composition" "r1" 'unknown digest-a
+ subject: "software/composite" claim: "claim/composite"
+ snapshot: "snapshot/composition"
+ evidence-kind: 'integration-test
+ capability: 'composition-test scope: "repository"
+ snapshot-revision: "r0"
+ snapshot-context-digest: digest-a))
+ (stale
+ (assurance-derive-composition-requirements
+ "composition/stale"
+ (construct
+ (list component-a component-b composite stale-obligation)
+ (cons direct-support components))))
+ (bound-obligation
+ (assurance-bind-obligation-to-snapshot
+ candidate-obligation candidate))
+ (bound-snapshot
+ (construct
+ (list component-a component-b composite bound-obligation)
+ (cons direct-support components)))
+ (declared
+ (assurance-derive-composition-requirements
+ "composition/declared" bound-snapshot))
+ (changed
+ (assurance-invalidate
+ "composition/changed" bound-snapshot
+ '("claim/component-a"))))
+ (check-equal? (assurance-composition-derivation? missing) #t)
+ (check-equal? (.ref missing 'digest) (.ref permuted 'digest))
+ (check-equal?
+ (.ref (car (.ref missing 'requirements)) 'component-claims)
+ '("claim/component-a" "claim/component-b"))
+ (check-equal?
+ (.ref (car (.ref missing 'requirements)) 'blocker)
+ 'missing-direct-obligation)
+ (check-equal?
+ (.ref (car (.ref stale 'requirements)) 'direct-obligations)
+ '("obligation/composition"))
+ (check-equal?
+ (.ref (car (.ref stale 'requirements)) 'current-obligations) '())
+ (check-equal?
+ (.ref (car (.ref stale 'requirements)) 'blocker)
+ 'stale-direct-obligation)
+ (check-equal?
+ (.ref (car (.ref declared 'requirements)) 'direct-obligations)
+ '("obligation/composition"))
+ (check-equal?
+ (.ref (car (.ref declared 'requirements)) 'current-obligations)
+ '("obligation/composition"))
+ (check-equal?
+ (.ref (car (.ref declared 'requirements)) 'blocker) #f)
+ (check-equal? (.ref declared 'verifier-executed?) #f)
+ (check-equal? (.ref declared 'release-authorized?) #f)
+ (check-equal?
+ (assurance-invalidation-receipt-ref changed 'required-obligations)
+ '("obligation/composition")))))
+
+ (test-case "POO Graph orders affected prerequisites before dependents"
+ (let* ((a (verification-obligation "obligation/a" 'gerbil-test))
+ (b (verification-obligation "obligation/b" 'gerbil-test))
+ (c (verification-obligation "obligation/c" 'gerbil-test))
+ (relations
+ (list (verification-support "obligation/a")
+ (verification-support "obligation/b")
+ (verification-support "obligation/c")
+ (verification-dependency "obligation/a" "obligation/b")
+ (verification-dependency "obligation/b" "obligation/c")))
+ (policy
+ (assurance-verification-policy
+ "policy/release" "r1" '(gerbil-test)))
+ (left
+ (assurance-plan-verification
+ "plan/ordered"
+ (verification-snapshot (list a b c) relations)
+ '("artifact/source") policy))
+ (right
+ (assurance-plan-verification
+ "plan/ordered"
+ (verification-snapshot (list c a b) (reverse relations))
+ '("artifact/source") policy)))
+ (check-equal? (.ref left 'selected-obligations)
+ '("obligation/c" "obligation/b" "obligation/a"))
+ (check-equal? (.ref left 'blocked-obligations) '())
+ (check-equal? (.ref left 'cycle-path) '())
+ (check-equal? (.ref left 'cyclic-components) '())
+ (check-equal? (.ref (car (.ref left 'requests)) 'dependencies) '())
+ (check-equal? (.ref (cadr (.ref left 'requests)) 'dependencies)
+ '("obligation/c"))
+ (check-equal? (.ref left 'digest) (.ref right 'digest))
+ (check-equal? (.ref left 'verifier-executed?) #f)))
+
+ (test-case "an unplanned prerequisite blocks its dependent"
+ (let* ((a (verification-obligation "obligation/a" 'gerbil-test))
+ (b (verification-obligation "obligation/b" 'gerbil-test))
+ (snapshot
+ (verification-snapshot
+ (list a b)
+ (list (verification-support "obligation/a")
+ (verification-dependency "obligation/a" "obligation/b"))))
+ (policy
+ (assurance-verification-policy
+ "policy/release" "r1" '(gerbil-test)))
+ (plan
+ (assurance-plan-verification
+ "plan/unplanned" snapshot '("artifact/source") policy)))
+ (check-equal? (.ref plan 'selected-obligations) '())
+ (check-equal? (.ref plan 'blocked-obligations) '("obligation/a"))
+ (check-equal? (.ref (car (.ref plan 'requests)) 'dependencies)
+ '("obligation/b"))
+ (check-equal? (.ref (car (.ref plan 'requests)) 'blocker)
+ 'dependency-unplanned)))
+
+ (test-case "a blocked prerequisite propagates along POO Graph reachability"
+ (let* ((a (verification-obligation "obligation/a" 'gerbil-test))
+ (b (verification-obligation "obligation/b" 'lean))
+ (snapshot
+ (verification-snapshot
+ (list a b)
+ (list (verification-support "obligation/a")
+ (verification-support "obligation/b")
+ (verification-dependency "obligation/a" "obligation/b"))))
+ (policy
+ (assurance-verification-policy
+ "policy/release" "r1" '(gerbil-test)))
+ (plan
+ (assurance-plan-verification
+ "plan/blocked-prerequisite" snapshot
+ '("artifact/source") policy)))
+ (check-equal? (.ref plan 'selected-obligations) '())
+ (check-equal? (.ref plan 'blocked-obligations)
+ '("obligation/b" "obligation/a"))
+ (check-equal? (.ref (car (.ref plan 'requests)) 'blocker)
+ 'capability-unavailable)
+ (check-equal? (.ref (cadr (.ref plan 'requests)) 'blocker)
+ 'dependency-blocked)))
+
+ (test-case "a dependency cycle blocks its SCC but preserves an independent branch"
+ (let* ((a (verification-obligation "obligation/a" 'gerbil-test))
+ (b (verification-obligation "obligation/b" 'gerbil-test))
+ (c (verification-obligation "obligation/c" 'gerbil-test))
+ (d (verification-obligation "obligation/d" 'gerbil-test))
+ (snapshot
+ (verification-snapshot
+ (list a b c d)
+ (list (verification-support "obligation/a")
+ (verification-support "obligation/b")
+ (verification-support "obligation/c")
+ (verification-support "obligation/d")
+ (verification-dependency "obligation/a" "obligation/b")
+ (verification-dependency "obligation/b" "obligation/a")
+ (verification-dependency "obligation/d" "obligation/a"))))
+ (policy
+ (assurance-verification-policy
+ "policy/release" "r1" '(gerbil-test)))
+ (plan
+ (assurance-plan-verification
+ "plan/cycle" snapshot '("artifact/source") policy)))
+ (check-equal? (.ref plan 'selected-obligations) '("obligation/c"))
+ (check-equal? (.ref plan 'blocked-obligations)
+ '("obligation/a" "obligation/b" "obligation/d"))
+ (check-equal? (.ref plan 'cycle-path)
+ '("obligation/a" "obligation/b" "obligation/a"))
+ (check-equal? (.ref plan 'cyclic-components)
+ '(("obligation/a" "obligation/b")))
+ (check-equal? (map (lambda (request) (.ref request 'blocker))
+ (.ref plan 'requests))
+ '(#f dependency-cycle dependency-cycle
+ dependency-blocked))))
+
+ (test-case "every independent dependency cycle is explained"
+ (let* ((ids '("obligation/a" "obligation/b" "obligation/c"
+ "obligation/d" "obligation/e" "obligation/f"
+ "obligation/g"))
+ (nodes (map (lambda (id)
+ (verification-obligation id 'gerbil-test))
+ ids))
+ (relations
+ (append
+ (map verification-support ids)
+ (list (verification-dependency "obligation/a" "obligation/b")
+ (verification-dependency "obligation/b" "obligation/a")
+ (verification-dependency "obligation/d" "obligation/a")
+ (verification-dependency "obligation/e" "obligation/f")
+ (verification-dependency "obligation/f" "obligation/e")
+ (verification-dependency "obligation/g" "obligation/e"))))
+ (policy
+ (assurance-verification-policy
+ "policy/release" "r1" '(gerbil-test)))
+ (left
+ (assurance-plan-verification
+ "plan/two-cycles"
+ (verification-snapshot nodes relations)
+ '("artifact/source") policy))
+ (right
+ (assurance-plan-verification
+ "plan/two-cycles"
+ (verification-snapshot (reverse nodes) (reverse relations))
+ '("artifact/source") policy)))
+ (check-equal? (.ref left 'selected-obligations) '("obligation/c"))
+ (check-equal? (.ref left 'blocked-obligations)
+ '("obligation/a" "obligation/b" "obligation/d"
+ "obligation/e" "obligation/f" "obligation/g"))
+ (check-equal? (.ref left 'cyclic-components)
+ '(("obligation/a" "obligation/b")
+ ("obligation/e" "obligation/f")))
+ (check-equal? (.ref left 'digest) (.ref right 'digest))
+ (check-equal? (.ref left 'cyclic-components)
+ (.ref right 'cyclic-components))))
+
+ (test-case "missing capability remains an explained blocked obligation"
+ (let* ((policy
+ (assurance-verification-policy "policy/release" "r1" '()))
+ (plan
+ (assurance-plan-verification
+ "plan/no-capability" full-software-snapshot
+ '("artifact/source") policy)))
+ (check-equal? (.ref plan 'selected-obligations) '())
+ (check-equal? (.ref plan 'blocked-obligations)
+ '("obligation/verify"))
+ (check-equal? (.ref (car (.ref plan 'requests)) 'blocker)
+ 'capability-unavailable)
+ (check-equal? (.ref plan 'release-authorized?) #f)))
+
+ (test-case "unresolved inputs and old obligation snapshots fail closed"
+ (let* ((policy
+ (assurance-verification-policy
+ "policy/release" "r1" '(gerbil-test)))
+ (unresolved-snapshot
+ (software-snapshot
+ (list artifact claim obligation evidence decision effect)
+ (list dependency discharge obligation-support
+ decision-dependency authorization)
+ '("artifact/missing")))
+ (unresolved-plan
+ (assurance-plan-verification
+ "plan/unresolved" unresolved-snapshot
+ '("artifact/source") policy))
+ (old-obligation
+ (.o (:: @ obligation) snapshot: "snapshot/old"))
+ (old-snapshot
+ (software-snapshot
+ (list artifact claim old-obligation evidence decision effect)
+ (list dependency discharge obligation-support
+ decision-dependency authorization)))
+ (old-plan
+ (assurance-plan-verification
+ "plan/old" old-snapshot '("artifact/source") policy)))
+ (check-equal? (.ref unresolved-plan 'selected-obligations) '())
+ (check-equal? (.ref unresolved-plan 'unresolved)
+ '("artifact/missing"))
+ (check-equal?
+ (.ref (car (.ref unresolved-plan 'requests)) 'blocker)
+ 'unresolved-frontier)
+ (check-equal? (.ref old-plan 'selected-obligations) '())
+ (check-equal? (.ref (car (.ref old-plan 'requests)) 'blocker)
+ 'stale-obligation)))
+
+ (test-case "same-identity snapshot revisions and contents cannot reuse old obligations"
+ (let* ((policy
+ (assurance-verification-policy
+ "policy/release" "r1" '(gerbil-test)))
+ (nodes (.ref full-software-snapshot 'nodes))
+ (relations (.ref full-software-snapshot 'relations))
+ (new-revision
+ (software-snapshot nodes relations '() "r2"))
+ (changed-content
+ (software-snapshot
+ (map (lambda (node)
+ (if (equal? (.ref node 'identity) "claim/release")
+ (.o (:: @ node) predicate: "release-ready-v2")
+ node))
+ nodes)
+ relations))
+ (revision-plan
+ (assurance-plan-verification
+ "plan/new-revision" new-revision
+ '("artifact/source") policy))
+ (content-plan
+ (assurance-plan-verification
+ "plan/changed-content" changed-content
+ '("artifact/source") policy)))
+ (check-equal? (.ref new-revision 'identity)
+ (.ref full-software-snapshot 'identity))
+ (check-equal? (.ref changed-content 'revision)
+ (.ref full-software-snapshot 'revision))
+ (check-equal? (.ref revision-plan 'selected-obligations) '())
+ (check-equal? (.ref content-plan 'selected-obligations) '())
+ (check-equal? (.ref (car (.ref revision-plan 'requests)) 'blocker)
+ 'stale-obligation)
+ (check-equal? (.ref (car (.ref content-plan 'requests)) 'blocker)
+ 'stale-obligation)
+ (check-equal? (.ref revision-plan 'verifier-executed?) #f)
+ (check-equal? (.ref content-plan 'release-authorized?) #f)
+ (check-exception
+ (assurance-bind-obligation-to-snapshot
+ (car (filter assurance-obligation? nodes)) new-revision)
+ Error?)))
+
+ (test-case "conflicted snapshot remains distinct from unknown frontier"
+ (let* ((snapshot
+ (assurance-snapshot
+ "snapshot/software" "r1" "graph/software"
+ '(("artifact/source" . "r2"))
+ '(("claim/release" . "r1"))
+ "event-cut/1" "policy/release" "r1"
+ (list artifact claim obligation evidence decision effect)
+ (list dependency discharge obligation-support
+ decision-dependency authorization)
+ evidence-identities: '("evidence/test")))
+ (policy
+ (assurance-verification-policy
+ "policy/release" "r1" '(gerbil-test)))
+ (plan
+ (assurance-plan-verification
+ "plan/conflict" snapshot '("artifact/source") policy)))
+ (check-equal? (.ref plan 'conflicts) '("artifact/source"))
+ (check-equal? (.ref plan 'unresolved) '())
+ (check-equal? (.ref plan 'selected-obligations) '())
+ (check-equal? (.ref (car (.ref plan 'requests)) 'blocker)
+ 'conflicted-snapshot)))
+
+ (test-case "verification policy cannot silently change snapshot authority"
+ (check-exception
+ (assurance-verification-policy "policy/release" "r1" '("gerbil-test"))
+ Error?)
+ (check-exception
+ (assurance-plan-verification
+ "plan/wrong-policy" full-software-snapshot
+ '("artifact/source")
+ (assurance-verification-policy
+ "policy/other" "r1" '(gerbil-test)))
+ Error?))
+
(test-case "structural cycles have deterministic SCCs and witnesses"
(let* ((snapshot
(software-snapshot
diff --git a/t/assurance/unit/decision-preflight-test.ss b/t/assurance/unit/decision-preflight-test.ss
new file mode 100644
index 0000000..fe02e8c
--- /dev/null
+++ b/t/assurance/unit/decision-preflight-test.ss
@@ -0,0 +1,228 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+(import :std/test :std/error
+ (only-in :clan/poo/object .o .ref)
+ (only-in :poo-flow/modules/authorization/providers/cedar/interface
+ poo-flow-cedar-runtime-handoff
+ poo-flow-cedar-authorization-request?
+ poo-flow-cedar-authorization-request->runtime)
+ :poo-flow/lambda-aitia/modules/assurance/interface)
+
+(export decision-preflight-test)
+
+(def digest-a (string-append "sha256:" (make-string 64 #\a)))
+(def digest-b (string-append "sha256:" (make-string 64 #\b)))
+(def artifact
+ (assurance-artifact "artifact/source" "r1" 'supported digest-a
+ owner: "maintainer" media-kind: 'scheme-source))
+(def claim
+ (assurance-claim "claim/release" "r1" 'unknown digest-b
+ subject: "software/release" predicate: "ready"
+ support-requirements: '("obligation/test")
+ scope: "repository" valid-from: "2026-09-23"))
+(def obligation
+ (assurance-obligation
+ "obligation/test" "r1" 'unknown digest-a
+ subject: "software/release" claim: "claim/release"
+ snapshot: "snapshot/software" evidence-kind: 'native-test
+ capability: 'gerbil-test scope: "repository"))
+(def evidence
+ (assurance-evidence
+ "evidence/test" "r1" 'supported digest-b
+ producer: "gxtest" tool: "gerbil-test" tool-version: "v19"
+ input-artifacts: '("artifact/source") obligation: "obligation/test"
+ subject: "software/release" scope: "repository"
+ valid-from: "2026-09-23" admission-state: 'candidate))
+(def decision
+ (assurance-decision
+ "decision/release" "r1" 'unknown digest-a
+ subject: "software/release" snapshot: "snapshot/software"
+ policy: "policy/release" authority: "maintainer" outcome: 'unknown))
+(def evidence-link
+ (assurance-relation "relation/discharge" 'assurance 'discharges
+ "evidence/test" "obligation/test" 'observed))
+(def requirement-link
+ (assurance-relation "relation/requirement" 'assurance 'supports
+ "obligation/test" "claim/release" 'declared))
+(def decision-link
+ (assurance-relation "relation/decision-dependency" 'structural 'depends-on
+ "decision/release" "claim/release" 'declared))
+(def relations (list evidence-link requirement-link decision-link))
+
+(def (construct obligation-value relation-values fact-cut-value)
+ (assurance-snapshot
+ "snapshot/software" "r1" "graph/software"
+ '(("artifact/source" . "r1")) '(("claim/release" . "r1"))
+ fact-cut-value "policy/release" "r1"
+ (list artifact claim obligation-value evidence decision) relation-values))
+(def (bound-cut relation-values)
+ (let (draft (construct obligation relation-values "event-cut/1"))
+ (construct (assurance-bind-obligation-to-snapshot obligation draft)
+ relation-values "event-cut/1")))
+(def (bound-obligation snapshot-value)
+ (let loop ((nodes (.ref snapshot-value 'nodes)))
+ (and (pair? nodes)
+ (if (equal? (.ref (car nodes) 'identity) "obligation/test")
+ (car nodes)
+ (loop (cdr nodes))))))
+(def (reported-outcome snapshot-value)
+ (assurance-verifier-outcome
+ "outcome/test" 'succeeded
+ producer: "gxtest" tool: "gerbil-test" tool-version: "v19"
+ obligation: "obligation/test" subject: "software/release"
+ scope: "repository" snapshot-revision: "r1"
+ snapshot-context-digest: (.ref snapshot-value 'context-digest)
+ inputs: (list (assurance-verifier-input artifact))
+ output-digest: digest-b))
+(def (make-host source)
+ (assurance-verification-host
+ "host/decision-preflight"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () 11) 9 source))
+(def handoff
+ (poo-flow-cedar-runtime-handoff
+ 1 #u8(1 2 3) digest-a digest-a digest-b digest-b))
+
+(def decision-preflight-test
+ (test-suite "Host Decision preflight is not authorization"
+ (test-case "exact undecided Decision and support issue one Host preflight"
+ (let* ((snapshot-value (bound-cut relations))
+ (host (make-host (lambda () snapshot-value)))
+ (admission
+ (assurance-host-admit
+ host (bound-obligation snapshot-value) evidence
+ (reported-outcome snapshot-value)))
+ (preflight
+ (assurance-host-preflight-decision
+ host decision claim (list admission)))
+ (other-host (make-host (lambda () snapshot-value))))
+ (check-equal? (assurance-decision-preflight? preflight) #t)
+ (check-equal? (.ref preflight 'release-authorized?) #f)
+ (check-equal?
+ (assurance-decision-preflight?
+ (.o (:: @ preflight) release-authorized?: #t)) #f)
+ (check-equal?
+ (assurance-host-decision-preflight-current? host preflight) #t)
+ (check-equal?
+ (assurance-host-decision-preflight-current?
+ host (.o (:: @ preflight))) #f)
+ (check-equal?
+ (assurance-host-decision-preflight-current?
+ other-host preflight) #f)
+ (check-equal?
+ (assurance-host-preflight-decision
+ host (.o (:: @ decision) outcome: 'allow)
+ claim (list admission)) #f)
+ (check-equal?
+ (assurance-host-preflight-decision host decision claim '()) #f)
+ (assurance-host-revoke-admission! host admission)
+ (check-equal?
+ (assurance-host-decision-preflight-current? host preflight) #f)))
+
+ (test-case "missing Decision dependency cannot preflight"
+ (let* ((snapshot-value
+ (bound-cut (list evidence-link requirement-link)))
+ (host (make-host (lambda () snapshot-value)))
+ (admission
+ (assurance-host-admit
+ host (bound-obligation snapshot-value) evidence
+ (reported-outcome snapshot-value))))
+ (check-equal?
+ (assurance-host-preflight-decision
+ host decision claim (list admission)) #f)))
+
+ (test-case "only a current Host preflight projects an inert Cedar request"
+ (let* ((snapshot-value (bound-cut relations))
+ (host (make-host (lambda () snapshot-value)))
+ (admission
+ (assurance-host-admit
+ host (bound-obligation snapshot-value) evidence
+ (reported-outcome snapshot-value)))
+ (preflight
+ (assurance-host-preflight-decision
+ host decision claim (list admission)))
+ (request
+ (assurance-host-cedar-request
+ host preflight "Release" digest-a handoff))
+ (other-host (make-host (lambda () snapshot-value)))
+ (runtime-value
+ (poo-flow-cedar-authorization-request->runtime request))
+ (context-value (hash-get runtime-value "context")))
+ (check-equal? (poo-flow-cedar-authorization-request? request) #t)
+ (check-equal? (assurance-host-cedar-request-current? host request) #t)
+ (check-equal?
+ (assurance-host-cedar-request-current?
+ host (.o (:: @ request))) #f)
+ (check-equal?
+ (assurance-host-cedar-request-current?
+ host (.o (:: @ request) action: "Deploy")) #f)
+ (check-equal?
+ (assurance-host-cedar-request-current? other-host request) #f)
+ (check-equal? (hash-get runtime-value "principal") "maintainer")
+ (check-equal? (hash-get runtime-value "resource") "software/release")
+ (check-equal? (hash-get context-value "aitia_preflight")
+ (.ref preflight 'identity))
+ (check-equal? (hash-get context-value "aitia_policy") "policy/release")
+ (check-equal? (hash-get context-value "aitia_preflight_only") #t)
+ (check-equal? (hash-get context-value "poo_flow_preflight_only") #t)
+ (check-equal?
+ (assurance-host-cedar-request
+ host (.o (:: @ preflight)) "Release" digest-a handoff) #f)
+ (check-exception
+ (assurance-host-cedar-request
+ host preflight "Release" digest-a (.o)) Error?)
+ (assurance-host-revoke-decision-preflight! host preflight)
+ (check-equal?
+ (assurance-host-cedar-request-current? host request) #f)
+ (check-equal?
+ (assurance-host-cedar-request
+ host preflight "Release" digest-a handoff) #f)))
+
+ (test-case "source change and rollback cannot revive a preflight"
+ (let* ((current (bound-cut relations))
+ (host (make-host (lambda () current)))
+ (admission
+ (assurance-host-admit
+ host (bound-obligation current) evidence
+ (reported-outcome current)))
+ (preflight
+ (assurance-host-preflight-decision
+ host decision claim (list admission)))
+ (request
+ (assurance-host-cedar-request
+ host preflight "Release" digest-a handoff))
+ (old-cut current))
+ (check-equal?
+ (assurance-host-decision-preflight-current? host preflight) #t)
+ (check-equal?
+ (assurance-host-cedar-request-current? host request) #t)
+ (set! current
+ (construct (bound-obligation old-cut) relations "event-cut/2"))
+ (check-equal?
+ (assurance-host-decision-preflight-current? host preflight) #f)
+ (check-equal?
+ (assurance-host-cedar-request-current? host request) #f)
+ (set! current old-cut)
+ (check-equal?
+ (assurance-host-decision-preflight-current? host preflight) #f)
+ (check-equal?
+ (assurance-host-cedar-request-current? host request) #f)))
+
+ (test-case "explicit preflight revocation is separate from evidence"
+ (let* ((snapshot-value (bound-cut relations))
+ (host (make-host (lambda () snapshot-value)))
+ (admission
+ (assurance-host-admit
+ host (bound-obligation snapshot-value) evidence
+ (reported-outcome snapshot-value)))
+ (preflight
+ (assurance-host-preflight-decision
+ host decision claim (list admission))))
+ (assurance-host-revoke-decision-preflight! host preflight)
+ (check-equal?
+ (assurance-host-decision-preflight-current? host preflight) #f)
+ (check-equal?
+ (assurance-host-admission-current? host admission) #t)))))
diff --git a/t/assurance/unit/evidence-admission-test.ss b/t/assurance/unit/evidence-admission-test.ss
new file mode 100644
index 0000000..de8a929
--- /dev/null
+++ b/t/assurance/unit/evidence-admission-test.ss
@@ -0,0 +1,593 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+(import :std/test
+ (only-in :std/error Error?)
+ (only-in :clan/poo/object .o .ref)
+ (only-in :poo-flow/core/contribution/verification
+ poo-flow-verification-adapter poo-flow-verify
+ poo-flow-revoke-verification!)
+ :poo-flow/lambda-aitia/modules/assurance/interface)
+
+(export evidence-admission-test)
+
+(def digest-a (string-append "sha256:" (make-string 64 #\a)))
+(def digest-b (string-append "sha256:" (make-string 64 #\b)))
+(def artifact
+ (assurance-artifact "artifact/source" "r1" 'supported digest-a
+ owner: "maintainer" media-kind: 'scheme-source))
+(def claim
+ (assurance-claim "claim/release" "r1" 'unknown digest-b
+ subject: "software/release" predicate: "release-ready"
+ scope: "repository" valid-from: "2026-09-23"))
+(def obligation
+ (assurance-obligation
+ "obligation/test" "r1" 'unknown digest-a
+ subject: "software/release" claim: "claim/release"
+ snapshot: "snapshot/software" evidence-kind: 'native-test
+ capability: 'gerbil-test scope: "repository"))
+(def (make-snapshot artifact-value obligation-value (revision "r1"))
+ (assurance-snapshot
+ "snapshot/software" revision "graph/software"
+ (list (cons "artifact/source" (.ref artifact-value 'revision)))
+ '(("claim/release" . "r1"))
+ "event-cut/1" "policy/release" "r1"
+ (list artifact-value claim obligation-value) '()))
+(def draft (make-snapshot artifact obligation))
+(def bound-obligation (assurance-bind-obligation-to-snapshot obligation draft))
+(def snapshot (make-snapshot artifact bound-obligation))
+(def outcome
+ (assurance-verifier-outcome
+ "outcome/test" 'succeeded
+ producer: "gxtest" tool: "gerbil-test" tool-version: "v19"
+ obligation: "obligation/test" subject: "software/release"
+ scope: "repository" snapshot-revision: "r1"
+ snapshot-context-digest: (.ref snapshot 'context-digest)
+ inputs: (list (assurance-verifier-input artifact))
+ output-digest: digest-b))
+(def trusted-evidence
+ (assurance-evidence
+ "evidence/test" "r1" 'supported digest-b
+ producer: "gxtest" tool: "gerbil-test" tool-version: "v19"
+ input-artifacts: '("artifact/source") obligation: "obligation/test"
+ subject: "software/release" scope: "repository"
+ valid-from: "2026-09-23" admission-state: 'admitted))
+(def trusted-discharge
+ (assurance-relation "relation/discharge" 'assurance 'discharges
+ "evidence/test" "obligation/test" 'observed))
+(def (make-trusted-snapshot obligation-value)
+ (assurance-snapshot
+ "snapshot/software" "r1" "graph/software"
+ '(("artifact/source" . "r1")) '(("claim/release" . "r1"))
+ "event-cut/1" "policy/release" "r1"
+ (list artifact claim obligation-value trusted-evidence)
+ (list trusted-discharge)
+ evidence-identities: '("evidence/test")))
+(def trusted-draft (make-trusted-snapshot obligation))
+(def trusted-obligation
+ (assurance-bind-obligation-to-snapshot obligation trusted-draft))
+(def trusted-snapshot (make-trusted-snapshot trusted-obligation))
+(def trusted-outcome
+ (.o (:: @ outcome)
+ snapshot-context-digest: (.ref trusted-snapshot 'context-digest)))
+(def candidate-evidence
+ (.o (:: @ trusted-evidence) admission-state: 'candidate))
+(def (make-candidate-snapshot source-value obligation-value revision-value)
+ (assurance-snapshot
+ "snapshot/software" revision-value "graph/software"
+ (list (cons "artifact/source" (.ref source-value 'revision)))
+ '(("claim/release" . "r1"))
+ "event-cut/1" "policy/release" "r1"
+ (list source-value claim obligation-value candidate-evidence)
+ (list trusted-discharge)))
+(def candidate-draft
+ (make-candidate-snapshot artifact obligation "r1"))
+(def candidate-obligation
+ (assurance-bind-obligation-to-snapshot obligation candidate-draft))
+(def candidate-snapshot
+ (make-candidate-snapshot artifact candidate-obligation "r1"))
+(def candidate-outcome
+ (.o (:: @ outcome)
+ snapshot-context-digest: (.ref candidate-snapshot 'context-digest)))
+(def trusted-adapter
+ (poo-flow-verification-adapter
+ "host/native-test"
+ (lambda (subject-value now until)
+ (and (eq? (.ref (.ref subject-value 'outcome) 'status) 'succeeded)
+ (equal? (.ref (.ref subject-value 'evidence) 'content-digest)
+ digest-b)))
+ assurance-verification-subject-snapshot))
+(def trusted-subject
+ (assurance-verification-subject
+ trusted-snapshot trusted-obligation trusted-evidence trusted-outcome))
+(def trusted-receipt
+ (poo-flow-verify trusted-adapter trusted-subject 10 20))
+(def untrusted-adapter
+ (poo-flow-verification-adapter
+ "other/native-test"
+ (lambda (subject-value now until) #t)
+ assurance-verification-subject-snapshot))
+(def (verified-support relation-value evidence-value obligation-value
+ snapshot-value outcome-value receipt-value now-value)
+ (assurance-support-sealed-under-adapter?
+ relation-value evidence-value obligation-value snapshot-value outcome-value
+ trusted-adapter receipt-value now-value))
+(def (blocker snapshot-value obligation-value outcome-value)
+ (.ref (assurance-evaluate-evidence-admission
+ snapshot-value obligation-value outcome-value)
+ 'blocker))
+
+(def evidence-admission-test
+ (test-suite "evidence admission eligibility"
+ (test-case "bound success is eligible but does not admit or authorize"
+ (let (receipt
+ (assurance-evaluate-evidence-admission
+ snapshot bound-obligation outcome))
+ (check-equal? (assurance-evidence-admission-receipt? receipt) #t)
+ (check-equal? (.ref receipt 'eligible?) #t)
+ (check-equal? (.ref receipt 'verifier-executed?) #f)
+ (check-equal? (.ref receipt 'release-authorized?) #f)
+ (check-equal? (.ref receipt 'snapshot-digest) (.ref snapshot 'digest))
+ (check-equal?
+ (.ref receipt 'digest)
+ (.ref (assurance-evaluate-evidence-admission
+ snapshot bound-obligation outcome)
+ 'digest))
+ (check-equal?
+ (equal? (.ref receipt 'digest)
+ (.ref (assurance-evaluate-evidence-admission
+ snapshot bound-obligation
+ (.o (:: @ outcome) output-digest: digest-a))
+ 'digest))
+ #f)))
+
+ (test-case "non-success statuses are typed, not green"
+ (for-each
+ (lambda (reported-status)
+ (check-equal?
+ (blocker snapshot bound-obligation
+ (.o (:: @ outcome) status: reported-status))
+ 'outcome-not-successful))
+ '(failed timed-out unavailable indeterminate)))
+
+ (test-case "green exit without output and bound inputs is rejected"
+ (check-equal?
+ (blocker snapshot bound-obligation
+ (.o (:: @ outcome) output-digest: #f))
+ 'missing-output)
+ (check-equal?
+ (blocker snapshot bound-obligation
+ (.o (:: @ outcome) inputs: '()))
+ 'missing-inputs)
+ (check-equal?
+ (blocker snapshot bound-obligation
+ (.o (:: @ outcome)
+ inputs: (list (assurance-verifier-input artifact)
+ (assurance-verifier-input artifact))))
+ 'duplicate-inputs))
+
+ (test-case "changed source revision and digest invalidate old result"
+ (let* ((new-artifact
+ (.o (:: @ artifact) revision: "r2" content-digest: digest-b))
+ (new-draft (make-snapshot new-artifact obligation "r2"))
+ (new-obligation
+ (assurance-bind-obligation-to-snapshot obligation new-draft))
+ (new-snapshot (make-snapshot new-artifact new-obligation "r2")))
+ (check-equal?
+ (blocker new-snapshot bound-obligation outcome)
+ 'stale-obligation)
+ (check-equal?
+ (blocker new-snapshot new-obligation
+ (.o (:: @ outcome)
+ snapshot-revision: "r2"
+ snapshot-context-digest:
+ (.ref new-snapshot 'context-digest)))
+ 'input-binding-mismatch)))
+
+ (test-case "same revision with changed artifact digest is not reusable"
+ (let* ((new-artifact (.o (:: @ artifact) content-digest: digest-b))
+ (new-draft (make-snapshot new-artifact obligation))
+ (new-obligation
+ (assurance-bind-obligation-to-snapshot obligation new-draft))
+ (new-snapshot (make-snapshot new-artifact new-obligation)))
+ (check-equal?
+ (blocker new-snapshot new-obligation outcome)
+ 'outcome-binding-mismatch)
+ (check-equal?
+ (blocker new-snapshot new-obligation
+ (.o (:: @ outcome)
+ snapshot-context-digest:
+ (.ref new-snapshot 'context-digest)))
+ 'input-binding-mismatch)))
+
+ (test-case "same revision with forged obligation semantics is stale"
+ (check-equal?
+ (blocker snapshot
+ (.o (:: @ bound-obligation) subject: "software/other")
+ outcome)
+ 'stale-obligation))
+
+ (test-case "conflicting snapshot never accepts a reported success"
+ (let (conflicted
+ (assurance-snapshot
+ "snapshot/software" "r1" "graph/software"
+ '(("artifact/source" . "r1")) '(("claim/release" . "r1"))
+ "event-cut/1" "policy/release" "r1"
+ (list artifact
+ (.o (:: @ artifact) content-digest: digest-b)
+ claim bound-obligation)
+ '()))
+ (check-equal?
+ (blocker conflicted bound-obligation outcome)
+ 'snapshot-conflicted)))
+
+ (test-case "outcome cannot retarget subject, scope or snapshot"
+ (check-equal?
+ (blocker snapshot bound-obligation
+ (.o (:: @ outcome) subject: "software/other"))
+ 'outcome-binding-mismatch)
+ (check-equal?
+ (blocker snapshot bound-obligation
+ (.o (:: @ outcome) snapshot-revision: "r0"))
+ 'outcome-binding-mismatch))
+
+ (test-case "human review names authority and exact scope"
+ (let* ((human-obligation
+ (.o (:: @ obligation) evidence-kind: 'human-review))
+ (human-draft (make-snapshot artifact human-obligation))
+ (human-bound
+ (assurance-bind-obligation-to-snapshot human-obligation human-draft))
+ (human-snapshot (make-snapshot artifact human-bound))
+ (human-outcome
+ (.o (:: @ outcome)
+ snapshot-context-digest:
+ (.ref human-snapshot 'context-digest))))
+ (check-equal?
+ (blocker human-snapshot human-bound human-outcome)
+ 'human-authority-missing)
+ (check-equal?
+ (blocker human-snapshot human-bound
+ (.o (:: @ human-outcome)
+ accountable-authority: "maintainer"
+ review-scope: "repository"))
+ #f)))
+
+ (test-case "support requires a sealed execution receipt"
+ (check-equal?
+ (verified-support
+ trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome #f 11)
+ #f)
+ (check-equal?
+ (verified-support
+ trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome trusted-receipt 11)
+ #t))
+
+ (test-case "copied seal and presentation mutation cannot forge support"
+ (check-equal?
+ (assurance-support-sealed-under-adapter?
+ trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome
+ (.o identity: "host/native-test"
+ admits?: (lambda (receipt-value subject-value now-value) #t))
+ trusted-receipt 11)
+ #f)
+ (check-equal?
+ (assurance-support-sealed-under-adapter?
+ trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome
+ (.o (:: @ trusted-adapter)) trusted-receipt 11)
+ #f)
+ (check-equal?
+ (verified-support
+ trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome
+ (.o (:: @ trusted-receipt)) 11)
+ #f)
+ (check-equal?
+ (verified-support
+ trusted-discharge trusted-evidence trusted-obligation
+ (.o (:: @ trusted-snapshot) state: 'supported)
+ trusted-outcome trusted-receipt 11)
+ #f)
+ (check-equal?
+ (verified-support
+ trusted-discharge
+ (.o (:: @ trusted-evidence) tool-version: "v20")
+ trusted-obligation trusted-snapshot trusted-outcome
+ trusted-receipt 11)
+ #f)
+ (check-equal?
+ (verified-support
+ trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot
+ (.o (:: @ trusted-outcome) tool-version: "v20")
+ trusted-receipt 11)
+ #f)
+ (check-equal?
+ (assurance-support-sealed-under-adapter?
+ trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome untrusted-adapter
+ trusted-receipt 11)
+ #f))
+
+ (test-case "configured Host owns adapter, clock and revocation"
+ (let* ((now 11)
+ (host
+ (assurance-verification-host
+ "host/assurance-test"
+ (lambda (subject-value issued-at expires-at)
+ (and (eq? (.ref (.ref subject-value 'outcome) 'status)
+ 'succeeded)
+ (equal? (.ref (.ref subject-value 'evidence)
+ 'content-digest)
+ digest-b)))
+ (lambda () now) 9 (lambda () trusted-snapshot)))
+ (receipt
+ (assurance-host-verify
+ host trusted-snapshot trusted-obligation trusted-evidence
+ trusted-outcome)))
+ (check-equal? (if receipt #t #f) #t)
+ (check-equal?
+ (assurance-host-verify
+ host trusted-snapshot trusted-obligation
+ (.o (:: @ trusted-evidence) tool-version: "v20")
+ trusted-outcome)
+ #f)
+ (check-equal?
+ (assurance-host-sealed-support?
+ host trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome receipt)
+ #t)
+ (check-exception
+ (assurance-host-sealed-support?
+ (.o (:: @ host)) trusted-discharge trusted-evidence
+ trusted-obligation trusted-snapshot trusted-outcome receipt)
+ Error?)
+ (check-exception
+ (assurance-host-sealed-support?
+ (.o identity: "host/assurance-test")
+ trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome receipt)
+ Error?)
+ (check-equal?
+ (assurance-host-verify
+ host trusted-snapshot trusted-obligation trusted-evidence
+ (.o (:: @ trusted-outcome) status: 'failed))
+ #f)
+ (set! now 20)
+ (check-equal?
+ (assurance-host-sealed-support?
+ host trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome receipt)
+ #f)
+ (set! now 11)
+ (check-exception
+ (assurance-host-sealed-support?
+ host trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome receipt)
+ Error?))
+ (let* ((host
+ (assurance-verification-host
+ "host/revocation-test"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () 11) 9 (lambda () trusted-snapshot)))
+ (receipt
+ (assurance-host-verify
+ host trusted-snapshot trusted-obligation trusted-evidence
+ trusted-outcome)))
+ (check-equal?
+ (assurance-host-sealed-support?
+ host trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome receipt)
+ #t)
+ (assurance-host-revoke! host receipt)
+ (check-equal?
+ (assurance-host-sealed-support?
+ host trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome receipt)
+ #f)))
+
+ (test-case "configured verifier refusal never issues a seal"
+ (let (host
+ (assurance-verification-host
+ "host/refusal-test"
+ (lambda (subject-value issued-at expires-at) #f)
+ (lambda () 11) 9 (lambda () trusted-snapshot)))
+ (check-equal?
+ (assurance-host-verify
+ host trusted-snapshot trusted-obligation trusted-evidence
+ trusted-outcome)
+ #f)))
+
+ (test-case "old Host seal stays invalid after a source cut returns"
+ (let* ((current trusted-snapshot)
+ (host
+ (assurance-verification-host
+ "host/seal-cut-test"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () 11) 9 (lambda () current)))
+ (receipt
+ (assurance-host-verify
+ host trusted-snapshot trusted-obligation trusted-evidence
+ trusted-outcome)))
+ (check-equal?
+ (assurance-host-sealed-support?
+ host trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome receipt)
+ #t)
+ (set! current (.o (:: @ trusted-snapshot) fact-cut: "event-cut/2"))
+ (check-exception
+ (assurance-host-sealed-support?
+ host trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome receipt)
+ Error?)
+ (set! current trusted-snapshot)
+ (check-equal?
+ (assurance-host-sealed-support?
+ host trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome receipt)
+ #f)))
+
+ (test-case "Host admission binds candidate evidence to an immutable cut"
+ (let* ((current candidate-snapshot)
+ (host
+ (assurance-verification-host
+ "host/admission-test"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () 11) 9 (lambda () current)))
+ (admission
+ (assurance-host-admit
+ host candidate-obligation candidate-evidence candidate-outcome)))
+ (check-equal? (if admission #t #f) #t)
+ (check-equal? (.ref candidate-evidence 'admission-state) 'candidate)
+ (check-equal? (assurance-host-admission-current? host admission) #t)
+ (check-equal?
+ (assurance-host-admission-current? host (.o (:: @ admission))) #f)
+ (check-equal?
+ (assurance-host-admission-current?
+ host (.o identity: (.ref admission 'identity))) #f)
+ (check-equal?
+ (assurance-host-admit
+ host candidate-obligation
+ (.o (:: @ candidate-evidence) tool-version: "v20")
+ candidate-outcome)
+ #f)
+ (let* ((new-source
+ (.o (:: @ artifact) revision: "r2" content-digest: digest-b))
+ (new-snapshot
+ (make-candidate-snapshot new-source obligation "r2")))
+ (set! current new-snapshot)
+ (check-equal?
+ (assurance-host-admission-current? host admission) #f)
+ (set! current candidate-snapshot)
+ (check-equal?
+ (assurance-host-admission-current? host admission) #f))))
+
+ (test-case "only the exact current assurance edge consumes a Host admission"
+ (let* ((now 11)
+ (current candidate-snapshot)
+ (host
+ (assurance-verification-host
+ "host/admitted-support-test"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () now) 9 (lambda () current)))
+ (other-host
+ (assurance-verification-host
+ "host/admitted-support-other"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () now) 9 (lambda () current)))
+ (admission
+ (assurance-host-admit
+ host candidate-obligation candidate-evidence candidate-outcome)))
+ (check-equal?
+ (assurance-host-admitted-support?
+ host trusted-discharge admission) #t)
+ (check-equal?
+ (assurance-host-admitted-support?
+ other-host trusted-discharge admission) #f)
+ (check-equal?
+ (assurance-host-admitted-support?
+ host trusted-discharge (.o (:: @ admission))) #f)
+ (check-equal?
+ (assurance-host-admitted-support?
+ host (.o (:: @ trusted-discharge) modality: 'hypothesized)
+ admission) #f)
+ (check-equal?
+ (assurance-host-admitted-support?
+ host (.o (:: @ trusted-discharge) target: "obligation/other")
+ admission) #f)
+ (set! now 20)
+ (check-equal?
+ (assurance-host-admitted-support?
+ host trusted-discharge admission) #f)))
+
+ (test-case "semantic snapshot mutation with unchanged display digest revokes"
+ (let* ((current candidate-snapshot)
+ (host
+ (assurance-verification-host
+ "host/semantic-cut-test"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () 11) 9 (lambda () current)))
+ (admission
+ (assurance-host-admit
+ host candidate-obligation candidate-evidence candidate-outcome)))
+ (check-equal? (assurance-host-admission-current? host admission) #t)
+ (set! current
+ (.o (:: @ candidate-snapshot) fact-cut: "event-cut/2"))
+ (check-equal? (.ref current 'digest)
+ (.ref candidate-snapshot 'digest))
+ (check-exception
+ (assurance-host-admission-current? host admission) Error?)
+ (set! current candidate-snapshot)
+ (check-equal? (assurance-host-admission-current? host admission) #f)))
+
+ (test-case "invalid Host snapshot source cannot revive an old admission"
+ (let* ((current candidate-snapshot)
+ (host
+ (assurance-verification-host
+ "host/invalid-source-test"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () 11) 9 (lambda () current)))
+ (admission
+ (assurance-host-admit
+ host candidate-obligation candidate-evidence candidate-outcome)))
+ (check-equal? (assurance-host-admission-current? host admission) #t)
+ (set! current #f)
+ (check-exception
+ (assurance-host-admission-current? host admission) Error?)
+ (set! current candidate-snapshot)
+ (check-equal? (assurance-host-admission-current? host admission) #f)))
+
+ (test-case "Host admission expires, revokes and refuses failed verification"
+ (let* ((now 11)
+ (host
+ (assurance-verification-host
+ "host/admission-expiry"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () now) 9 (lambda () candidate-snapshot)))
+ (admission
+ (assurance-host-admit
+ host candidate-obligation candidate-evidence candidate-outcome)))
+ (check-equal? (assurance-host-admission-current? host admission) #t)
+ (set! now 20)
+ (check-equal? (assurance-host-admission-current? host admission) #f)
+ (set! now 11)
+ (check-exception
+ (assurance-host-admission-current? host admission) Error?))
+ (let* ((host
+ (assurance-verification-host
+ "host/admission-revocation"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () 11) 9 (lambda () candidate-snapshot)))
+ (admission
+ (assurance-host-admit
+ host candidate-obligation candidate-evidence candidate-outcome)))
+ (check-equal? (assurance-host-admission-current? host admission) #t)
+ (assurance-host-revoke-admission! host admission)
+ (check-equal? (assurance-host-admission-current? host admission) #f))
+ (let (host
+ (assurance-verification-host
+ "host/admission-refusal"
+ (lambda (subject-value issued-at expires-at) #f)
+ (lambda () 11) 9 (lambda () candidate-snapshot)))
+ (check-equal?
+ (assurance-host-admit
+ host candidate-obligation candidate-evidence candidate-outcome)
+ #f)))
+
+ (test-case "expiry and revocation remove verified support"
+ (check-equal?
+ (verified-support
+ trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome trusted-receipt 20)
+ #f)
+ (poo-flow-revoke-verification! trusted-adapter trusted-receipt)
+ (check-equal?
+ (verified-support
+ trusted-discharge trusted-evidence trusted-obligation
+ trusted-snapshot trusted-outcome trusted-receipt 11)
+ #f))))
diff --git a/t/assurance/unit/lifecycle-boundary-test.ss b/t/assurance/unit/lifecycle-boundary-test.ss
new file mode 100644
index 0000000..3f99e2d
--- /dev/null
+++ b/t/assurance/unit/lifecycle-boundary-test.ss
@@ -0,0 +1,212 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+(import :std/test :std/error (only-in :clan/poo/object .o .ref)
+ :poo-flow/lambda-aitia/modules/assurance/interface
+ :poo-flow/lambda-aitia/t/formal/lifecycle-fixture)
+(export lifecycle-boundary-test)
+(def (drive fixture actions)
+ (for-each (lambda (action) (check (lifecycle-action! fixture action) => #t)) actions)
+ fixture)
+(def (ready)
+ (drive (make-lifecycle-fixture) '(InstallAdapter Plan Verify Admit Decide Authorize)))
+(def lifecycle-boundary-test
+ (test-suite "Lifecycle authorization, reservation and delivery boundaries"
+ (test-case "Admit consumes the prior seal without executing or renewing verification"
+ (let (f (drive (make-lifecycle-fixture) '(InstallAdapter Plan Verify)))
+ (check (lifecycle-get f 'verifier-calls) => 1)
+ (check (lifecycle-action! f 'Admit) => #t)
+ (check (lifecycle-get f 'verifier-calls) => 1)
+ (check (.ref (lifecycle-get f 'admission) 'expires-at) => 1))
+ (let (f (drive (make-lifecycle-fixture) '(InstallAdapter Plan Verify Tick)))
+ (check (lifecycle-action! f 'Admit) => #f)
+ (check (lifecycle-get f 'verifier-calls) => 1)))
+ (test-case "untrusted report cannot admit authorize reserve or deliver"
+ (let (f (drive (make-lifecycle-fixture) '(ReportSuccess)))
+ (check (lifecycle-action! f 'UnsafeLateAdmit) => #f)
+ (check (lifecycle-action! f 'UnsafeEarlyGrant) => #f)
+ (check (lifecycle-action! f 'UnsafeStart) => #f)
+ (check (.ref (lifecycle-action! f 'UnsafeBeforeStart) 'outcome) => 'rejected)
+ (check (lifecycle-get f 'authorization-calls) => 0)
+ (check (lifecycle-get f 'delivery-calls) => 0)))
+ (test-case "copied seals and foreign Host seals cannot admit"
+ (let* ((f (drive (make-lifecycle-fixture) '(InstallAdapter Plan Verify)))
+ (other (drive (make-lifecycle-fixture) '(InstallAdapter Plan Verify)))
+ (seal (lifecycle-get f 'seal)))
+ (lifecycle-set! f 'seal (.o (:: @ seal)))
+ (check (lifecycle-action! f 'Admit) => #f)
+ (lifecycle-set! f 'seal (lifecycle-get other 'seal))
+ (check (lifecycle-action! f 'Admit) => #f)))
+ (test-case "deny and indeterminate never receive a grant"
+ (for-each
+ (lambda (mode)
+ (let (f (drive (make-lifecycle-fixture) '(InstallAdapter Plan Verify Admit Decide)))
+ (lifecycle-set! f 'policy-mode mode)
+ (check (lifecycle-action! f 'Authorize) => #f)
+ (check (lifecycle-get f 'authorization-calls) => 1)
+ (check (lifecycle-action! f 'StartEffect) => #f)
+ (check (lifecycle-get f 'delivery-calls) => 0))) '(forbid indeterminate)))
+ (test-case "reserve is not delivery; current reservation hands off exactly once"
+ (let (f (ready))
+ (check (lifecycle-action! f 'StartEffect) => #t)
+ (check (lifecycle-get f 'service-effects) => 0)
+ (check (lifecycle-action! f 'StartEffect) => #f)
+ (check (.ref (lifecycle-action! f 'DeliverEffect) 'delivered?) => #t)
+ (check (.ref (lifecycle-action! f 'UnsafeReplay) 'outcome) => 'rejected)
+ (check (lifecycle-get f 'delivery-calls) => 1)
+ (check (lifecycle-get f 'service-effects) => 1)))
+ (test-case "renewed verification does not renew an old grant or reservation"
+ (let (f (ready))
+ (check (lifecycle-action! f 'Verify) => #t)
+ (check (lifecycle-action! f 'StartEffect) => #t)
+ (check (.ref (lifecycle-action! f 'DeliverEffect) 'delivered?) => #t))
+ (let (f (drive (ready) '(StartEffect Tick Verify Admit Decide Authorize)))
+ (check (.ref (lifecycle-get f 'grant) 'expires-at) => 2)
+ (check (.ref (lifecycle-action! f 'DeliverEffect) 'outcome) => 'rejected)
+ (check (lifecycle-get f 'delivery-calls) => 0)))
+ (test-case "expiry revoke and source change between reserve and delivery fail closed"
+ (for-each
+ (lambda (change)
+ (let (f (drive (ready) '(StartEffect)))
+ (check (lifecycle-action! f change) => #t)
+ (check (.ref (lifecycle-action! f 'UnsafeDelivery) 'outcome) => 'rejected)
+ (check (lifecycle-get f 'delivery-calls) => 0)
+ (check (lifecycle-get f 'service-effects) => 0))) '(Tick Revoke ChangeSource)))
+ (test-case "source change during authorization cannot publish a grant"
+ (let (f (drive (make-lifecycle-fixture) '(InstallAdapter Plan Verify Admit Decide)))
+ (lifecycle-set! f 'authorization-hook
+ (lambda () (lifecycle-set! f 'cut (lifecycle-cut 2))))
+ (check (lifecycle-action! f 'Authorize) => #f)
+ (check (lifecycle-get f 'delivery-calls) => 0)))
+ (test-case "permit cannot publish after policy callback expiry or request mutation"
+ (for-each
+ (lambda (change)
+ (let* ((f (ready)) (request (lifecycle-get f 'request)))
+ (lifecycle-set! f 'authorization-hook
+ (lambda ()
+ (case change
+ ((expiry) (lifecycle-set! f 'clock 1))
+ ((request) (string-set! (.ref request 'action) 0 #\X)))))
+ (check (assurance-effect-host-authorize! (lifecycle-get f 'effect-host)
+ (lifecycle-get f 'verification-host) request) => #f)
+ (check (lifecycle-get f 'authorization-calls) => 2)
+ (check (lifecycle-action! f 'StartEffect) => #f)
+ (check (lifecycle-get f 'delivery-calls) => 0))) '(expiry request)))
+ (test-case "copied grant extended expiry and copied reservation are refused"
+ (let* ((f (ready)) (host (lifecycle-get f 'effect-host))
+ (grant (lifecycle-get f 'grant)))
+ (check (assurance-effect-host-reserve! host (.o (:: @ grant))) => #f)
+ (check (assurance-effect-host-reserve! host (.o (:: @ grant) expires-at: 99)) => #f)
+ (let (reservation (assurance-effect-host-reserve! host grant))
+ (check (.ref (assurance-effect-host-deliver! host (.o (:: @ reservation))) 'outcome) => 'rejected)
+ (check (.ref (assurance-effect-host-deliver! host reservation) 'delivered?) => #t))))
+ (test-case "foreign Host grant and reservation never reach a runtime callback"
+ (let* ((f (ready)) (other (ready)) (host (lifecycle-get other 'effect-host))
+ (grant (lifecycle-get f 'grant)))
+ (check (assurance-effect-host-reserve! host grant) => #f)
+ (check (lifecycle-action! f 'StartEffect) => #t)
+ (check (.ref (assurance-effect-host-deliver! host (lifecycle-get f 'reservation)) 'outcome) => 'rejected)
+ (check (lifecycle-get other 'delivery-calls) => 0)
+ (check (.ref (lifecycle-action! f 'DeliverEffect) 'delivered?) => #t)))
+ (test-case "issued token binding mutation fails closed, including after reservation"
+ (for-each
+ (lambda (reserve-first?)
+ (let* ((f (ready)) (grant (lifecycle-get f 'grant))
+ (binding (.ref (.ref grant 'token) 'binding))
+ (digest (.ref binding 'bundle-digest)))
+ (when reserve-first? (check (lifecycle-action! f 'StartEffect) => #t))
+ (string-set! digest 0 (if (char=? (string-ref digest 0) #\a) #\b #\a))
+ (if reserve-first?
+ (check (.ref (lifecycle-action! f 'DeliverEffect) 'outcome) => 'rejected)
+ (check (lifecycle-action! f 'StartEffect) => #f))
+ (check (lifecycle-get f 'delivery-calls) => 0))) '(#f #t)))
+ (test-case "copied and intent-substituted requests cannot invoke authorization"
+ (let* ((f (ready)) (host (lifecycle-get f 'effect-host))
+ (vh (lifecycle-get f 'verification-host)) (request (lifecycle-get f 'request)))
+ (check (assurance-effect-host-authorize! host vh (.o (:: @ request))) => #f)
+ (check (assurance-effect-host-authorize! host vh (.o (:: @ request) intent: "other")) => #f)
+ (check (lifecycle-get f 'authorization-calls) => 1)))
+ (test-case "observed source ABA cannot revive an old grant"
+ (let* ((f (ready)) (cut (lifecycle-get f 'cut)))
+ (lifecycle-set! f 'cut (lifecycle-cut 2))
+ (check (lifecycle-action! f 'StartEffect) => #f)
+ (lifecycle-set! f 'cut cut)
+ (check (lifecycle-action! f 'StartEffect) => #f)
+ (check (lifecycle-get f 'delivery-calls) => 0)))
+ (test-case "in-place request string and handoff payload changes revoke the frozen request"
+ (for-each
+ (lambda (field)
+ (let* ((f (ready)) (request (lifecycle-get f 'request))
+ (text (if (eq? field 'payload-hex)
+ (.ref (.ref request 'handoff) field) (.ref request field))))
+ (string-set! text 0 (if (char=? (string-ref text 0) #\a) #\b #\a))
+ (check (assurance-host-cedar-request-current? (lifecycle-get f 'verification-host) request) => #f)
+ (check (assurance-effect-host-authorize! (lifecycle-get f 'effect-host)
+ (lifecycle-get f 'verification-host) request) => #f)
+ (check (lifecycle-action! f 'StartEffect) => #f)
+ (check (lifecycle-get f 'authorization-calls) => 1)
+ (check (lifecycle-get f 'delivery-calls) => 0))) '(intent action payload-hex)))
+ (test-case "authorization exception releases the fence without publishing authority"
+ (let (f (drive (make-lifecycle-fixture) '(InstallAdapter Plan Verify Admit Decide)))
+ (lifecycle-set! f 'authorization-hook (lambda () (error "policy runtime failure")))
+ (check-exception (lifecycle-action! f 'Authorize) Error?)
+ (check (lifecycle-get f 'grant) => #f)
+ (lifecycle-set! f 'authorization-hook #f)
+ (check (lifecycle-action! f 'Authorize) => #t)
+ (check (lifecycle-get f 'delivery-calls) => 0)))
+ (test-case "unknown acknowledgement is not delivered and cannot be retried"
+ (let (f (drive (ready) '(StartEffect)))
+ (check (.ref (lifecycle-action! f 'UnknownDelivery) 'delivered?) => #f)
+ (check (lifecycle-get f 'service-effects) => 1)
+ (check (.ref (lifecycle-action! f 'UnsafeReplay) 'outcome) => 'rejected)
+ (check (lifecycle-get f 'service-effects) => 1)))
+ (test-case "failed delivery is attributable failure, not success or retry authority"
+ (let (f (drive (ready) '(StartEffect)))
+ (check (.ref (lifecycle-action! f 'FailDelivery) 'outcome) => 'failed)
+ (check (lifecycle-get f 'service-effects) => 0)
+ (check (.ref (lifecycle-action! f 'UnsafeReplay) 'outcome) => 'rejected)
+ (check (lifecycle-get f 'delivery-calls) => 1)))
+ (test-case "malformed acknowledgement consumes the attempt without success or retry"
+ (let (f (drive (ready) '(StartEffect)))
+ ;; Use the API directly: the model fixture deliberately chooses only
+ ;; its three typed runtime outcomes.
+ (lifecycle-set! f 'delivery-mode 'not-an-acknowledgement)
+ (check-exception (assurance-effect-host-deliver! (lifecycle-get f 'effect-host)
+ (lifecycle-get f 'reservation)) Error?)
+ (check (.ref (lifecycle-action! f 'UnsafeReplay) 'outcome) => 'rejected)
+ (check (lifecycle-get f 'delivery-calls) => 1)))
+ (test-case "deadline crossed during grant validation cannot reserve or deliver"
+ (for-each
+ (lambda (reserve-first?)
+ (let ((f (ready)) (reads 0))
+ (when reserve-first? (check (lifecycle-action! f 'StartEffect) => #t))
+ ;; The effect clock advances from 0 to the strict deadline during
+ ;; this API call. The verification Host remains current at 0.
+ (lifecycle-set! f 'effect-clock-hook
+ (lambda ()
+ (let (now (min reads 1))
+ (set! reads (+ reads 1))
+ now)))
+ (if reserve-first?
+ (check (.ref (lifecycle-action! f 'DeliverEffect) 'outcome) => 'rejected)
+ (check (lifecycle-action! f 'StartEffect) => #f))
+ (check reads => 2)
+ (check (lifecycle-get f 'delivery-calls) => 0)
+ (check (lifecycle-get f 'service-effects) => 0))) '(#f #t)))
+ (test-case "clock rollback cannot restore an expired grant"
+ (let* ((f (ready)) (host (lifecycle-get f 'effect-host)))
+ (lifecycle-set! f 'clock 1)
+ (check (assurance-effect-host-grant-current? host (lifecycle-get f 'grant)) => #f)
+ (lifecycle-set! f 'clock 0)
+ (check-exception (lifecycle-action! f 'StartEffect) Error?)
+ (check (lifecycle-get f 'delivery-calls) => 0)))
+ (test-case "throwing and reentrant delivery stays consumed and releases the transient fence"
+ (let (f (drive (ready) '(StartEffect)))
+ (lifecycle-set! f 'delivery-hook
+ (lambda ()
+ (check-exception (assurance-effect-host-revoke! (lifecycle-get f 'effect-host)) Error?)
+ (error "runtime failure")))
+ (check-exception (lifecycle-action! f 'DeliverEffect) Error?)
+ (check (.ref (lifecycle-action! f 'UnsafeReplay) 'outcome) => 'rejected)
+ (assurance-effect-host-revoke! (lifecycle-get f 'effect-host))
+ (check (lifecycle-get f 'delivery-calls) => 1)))))
diff --git a/t/assurance/unit/required-support-test.ss b/t/assurance/unit/required-support-test.ss
new file mode 100644
index 0000000..a721fd7
--- /dev/null
+++ b/t/assurance/unit/required-support-test.ss
@@ -0,0 +1,183 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+(import :std/test
+ (only-in :std/list/list find)
+ (only-in :clan/poo/object .o .ref)
+ (only-in :poo-flow/core/poo-clos/interface
+ poo-clos-model-prototype)
+ :poo-flow/lambda-aitia/modules/assurance/interface)
+
+(export required-support-test)
+
+(def digest-a (string-append "sha256:" (make-string 64 #\a)))
+(def digest-b (string-append "sha256:" (make-string 64 #\b)))
+(def artifact
+ (assurance-artifact "artifact/source" "r1" 'supported digest-a
+ owner: "maintainer" media-kind: 'scheme-source))
+(def claim
+ (assurance-claim "claim/release" "r1" 'unknown digest-b
+ subject: "software/release" predicate: "ready"
+ support-requirements: '("obligation/test")
+ scope: "repository" valid-from: "2026-09-23"))
+(def obligation
+ (assurance-obligation
+ "obligation/test" "r1" 'unknown digest-a
+ subject: "software/release" claim: "claim/release"
+ snapshot: "snapshot/software" evidence-kind: 'native-test
+ capability: 'gerbil-test scope: "repository"))
+(def evidence
+ (assurance-evidence
+ "evidence/test" "r1" 'supported digest-b
+ producer: "gxtest" tool: "gerbil-test" tool-version: "v19"
+ input-artifacts: '("artifact/source") obligation: "obligation/test"
+ subject: "software/release" scope: "repository"
+ valid-from: "2026-09-23" admission-state: 'candidate))
+(def evidence-link
+ (assurance-relation "relation/discharge" 'assurance 'discharges
+ "evidence/test" "obligation/test" 'observed))
+(def requirement-link
+ (assurance-relation "relation/requirement" 'assurance 'supports
+ "obligation/test" "claim/release" 'declared))
+(def relations (list evidence-link requirement-link))
+
+(def (construct claim-value obligation-value relation-values)
+ (assurance-snapshot
+ "snapshot/software" "r1" "graph/software"
+ '(("artifact/source" . "r1")) '(("claim/release" . "r1"))
+ "event-cut/1" "policy/release" "r1"
+ (list artifact claim-value obligation-value evidence) relation-values))
+(def (bound-cut claim-value relation-values)
+ (let (draft (construct claim-value obligation relation-values))
+ (construct claim-value
+ (assurance-bind-obligation-to-snapshot obligation draft)
+ relation-values)))
+(def (bound-obligation snapshot)
+ (find (lambda (node)
+ (equal? (.ref node 'identity) "obligation/test"))
+ (.ref snapshot 'nodes)))
+(def (reported-outcome snapshot)
+ (assurance-verifier-outcome
+ "outcome/test" 'succeeded
+ producer: "gxtest" tool: "gerbil-test" tool-version: "v19"
+ obligation: "obligation/test" subject: "software/release"
+ scope: "repository" snapshot-revision: "r1"
+ snapshot-context-digest: (.ref snapshot 'context-digest)
+ inputs: (list (assurance-verifier-input artifact))
+ output-digest: digest-b))
+(def (make-host source)
+ (assurance-verification-host
+ "host/required-support"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () 11) 9 source))
+(def (first-blocker receipt)
+ (.ref (car (.ref receipt 'requirements)) 'blocker))
+
+(def required-support-test
+ (test-suite "Host required support is an inert Decision input"
+ (test-case "row model is well formed"
+ (let (row (.o (:: @ (poo-clos-model-prototype AssessmentRequiredSupport))
+ obligation: "obligation/test" supported?: #t blocker: #f))
+ (check-equal? (assessment-required-support? row) #t)))
+ (test-case "exact admission and both relations close declared support only"
+ (let* ((snapshot (bound-cut claim relations))
+ (host (make-host (lambda () snapshot)))
+ (admission
+ (assurance-host-admit
+ host (bound-obligation snapshot) evidence
+ (reported-outcome snapshot)))
+ (complete
+ (assurance-host-required-support host claim (list admission)))
+ (missing (assurance-host-required-support host claim '()))
+ (copied
+ (assurance-host-required-support
+ host claim (list (.o (:: @ admission))))))
+ (check-equal? (assurance-required-support? complete) #t)
+ (check-equal? (.ref complete 'complete?) #t)
+ (check-equal? (.ref complete 'release-authorized?) #f)
+ (check-equal?
+ (assurance-required-support?
+ (.o (:: @ complete) release-authorized?: #t)) #f)
+ (check-equal? (first-blocker missing) 'admission-missing)
+ (check-equal? (first-blocker copied) 'admission-missing)))
+
+ (test-case "undeclared and disconnected requirements stay blocked"
+ (let* ((snapshot (bound-cut claim relations))
+ (host (make-host (lambda () snapshot)))
+ (admission
+ (assurance-host-admit
+ host (bound-obligation snapshot) evidence
+ (reported-outcome snapshot)))
+ (two-required
+ (.o (:: @ claim)
+ support-requirements:
+ '("obligation/test" "obligation/independent")))
+ (two-cut (bound-cut two-required relations))
+ (two-host (make-host (lambda () two-cut)))
+ (assessment
+ (assurance-host-required-support
+ two-host two-required (list admission)))
+ (no-link-cut (bound-cut claim (list evidence-link)))
+ (no-link-host (make-host (lambda () no-link-cut)))
+ (no-link-admission
+ (assurance-host-admit
+ no-link-host (bound-obligation no-link-cut) evidence
+ (reported-outcome no-link-cut)))
+ (disconnected
+ (assurance-host-required-support
+ no-link-host claim (list no-link-admission))))
+ (check-equal? (.ref assessment 'complete?) #f)
+ (check-equal?
+ (.ref (cadr (.ref assessment 'requirements)) 'blocker)
+ 'obligation-missing)
+ (check-equal? (first-blocker disconnected) 'support-link-missing)))
+
+ (test-case "assumption closure is not inferred from verifier admission"
+ (let* ((assumed
+ (.o (:: @ claim) assumptions: '("assumption/toolchain")))
+ (snapshot (bound-cut assumed relations))
+ (host (make-host (lambda () snapshot)))
+ (admission
+ (assurance-host-admit
+ host (bound-obligation snapshot) evidence
+ (reported-outcome snapshot)))
+ (assessment
+ (assurance-host-required-support host assumed (list admission))))
+ (check-equal? (.ref assessment 'complete?) #f)
+ (check-equal?
+ (if (memq 'assumptions-unresolved (.ref assessment 'blockers)) #t #f)
+ #t)))
+
+ (test-case "expiry and revocation reopen required support"
+ (let* ((now 11)
+ (snapshot (bound-cut claim relations))
+ (host
+ (assurance-verification-host
+ "host/required-support-expiry"
+ (lambda (subject-value issued-at expires-at) #t)
+ (lambda () now) 9 (lambda () snapshot)))
+ (admission
+ (assurance-host-admit
+ host (bound-obligation snapshot) evidence
+ (reported-outcome snapshot))))
+ (check-equal?
+ (.ref (assurance-host-required-support host claim (list admission))
+ 'complete?) #t)
+ (set! now 20)
+ (check-equal?
+ (first-blocker
+ (assurance-host-required-support host claim (list admission)))
+ 'admission-invalid))
+ (let* ((snapshot (bound-cut claim relations))
+ (host (make-host (lambda () snapshot)))
+ (admission
+ (assurance-host-admit
+ host (bound-obligation snapshot) evidence
+ (reported-outcome snapshot))))
+ (assurance-host-revoke-admission! host admission)
+ (check-equal?
+ (first-blocker
+ (assurance-host-required-support host claim (list admission)))
+ 'admission-missing)))))
diff --git a/t/bindings/unit/aitia-native-test.ss b/t/bindings/unit/aitia-native-test.ss
index f209e52..3da237a 100644
--- a/t/bindings/unit/aitia-native-test.ss
+++ b/t/bindings/unit/aitia-native-test.ss
@@ -20,7 +20,7 @@
(string->json payload +aitia-native-test-json-read-options+))
(def +accepted-change+
- "{\"event\":\"pull-request\",\"repository\":\"tao3k/poo-flow\",\"revision\":\"0123456789abcdef\",\"source-ref\":\"feature/aitia\",\"target-ref\":\"develop\",\"pull-request\":42,\"checks\":[{\"name\":\"commit-policy\",\"conclusion\":\"success\"},{\"name\":\"build\",\"conclusion\":\"success\"},{\"name\":\"unit-test\",\"conclusion\":\"success\"},{\"name\":\"nasa-7150-2d\",\"conclusion\":\"success\"}]}")
+ "{\"event\":\"pull-request\",\"repository\":\"tao3k/poo-flow\",\"revision\":\"0123456789abcdef\",\"source-ref\":\"feature/aitia\",\"target-ref\":\"develop\",\"pull-request\":42,\"checks\":[{\"name\":\"commit-policy\",\"conclusion\":\"success\"},{\"name\":\"build\",\"conclusion\":\"success\"},{\"name\":\"unit-test\",\"conclusion\":\"success\"},{\"name\":\"nasa/sdlc/npr-7150.2\",\"conclusion\":\"success\",\"standardEdition\":\"D\",\"sourceLockDigest\":\"sha256:cfb963b8cd81fd8e22e9b47251c1dc7927b85fcdf9ec9366057cd38cb94f338c\"}]}")
(def aitia-native-test
(test-suite "Aitia Scheme-native C ABI"
diff --git a/t/fixtures/composition-value/aitia.ss b/t/fixtures/composition-value/aitia.ss
deleted file mode 100644
index c110098..0000000
--- a/t/fixtures/composition-value/aitia.ss
+++ /dev/null
@@ -1,8 +0,0 @@
-;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
-;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
-
-(use-composition aitia
- (project
- repository: "tao3k/lambda-aitia"
- languages: '(scheme python lean tla+))
- (delivery provider: 'github))
diff --git a/t/formal/lifecycle-fixture.ss b/t/formal/lifecycle-fixture.ss
new file mode 100644
index 0000000..a577d19
--- /dev/null
+++ b/t/formal/lifecycle-fixture.ss
@@ -0,0 +1,189 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; This fixture drives real plan / verification / admission / preflight /
+;;; token / reservation APIs. It never imports model expected states.
+;;; Configured local runtime callbacks are controls, not a native Cedar proof.
+(import :std/test (only-in :clan/poo/object .o .ref)
+ :poo-flow/lambda-aitia/modules/assurance/interface
+ (only-in :poo-flow/modules/authorization/providers/cedar/interface
+ poo-flow-cedar-decision poo-flow-cedar-runtime-handoff))
+(export make-lifecycle-fixture lifecycle-action! lifecycle-state
+ lifecycle-get lifecycle-set! lifecycle-cut)
+
+(def digest-a (string-append "sha256:" (make-string 64 #\a)))
+(def digest-b (string-append "sha256:" (make-string 64 #\b)))
+(def (lifecycle-cut revision)
+ (let* ((rev (number->string revision))
+ (artifact (assurance-artifact "source" rev 'supported
+ (if (= revision 1) digest-a digest-b) owner: "fixture" media-kind: 'scheme-source))
+ (claim (assurance-claim "claim" rev 'unknown digest-b subject: "release" predicate: "ready"
+ support-requirements: '("test") scope: "repo" valid-from: "2026-10-11"))
+ (obligation (assurance-obligation "test" rev 'unknown digest-a subject: "release"
+ claim: "claim" snapshot: "cut" evidence-kind: 'native-test
+ capability: 'gerbil-test scope: "repo"))
+ (evidence (assurance-evidence "evidence" rev 'supported digest-b producer: "fixture"
+ tool: "local-verifier" tool-version: "1" input-artifacts: '("source")
+ obligation: "test" subject: "release" scope: "repo"
+ valid-from: "2026-10-11" admission-state: 'candidate))
+ (decision (assurance-decision "decision" rev 'unknown digest-a subject: "release"
+ snapshot: "cut" policy: "policy" authority: "owner" outcome: 'unknown))
+ (relations (list
+ (assurance-relation "input" 'structural 'depends-on "claim" "source" 'declared)
+ (assurance-relation "support" 'assurance 'supports "test" "claim" 'declared)
+ (assurance-relation "discharge" 'assurance 'discharges "evidence" "test" 'observed)
+ (assurance-relation "decision-input" 'structural 'depends-on "decision" "claim" 'declared))))
+ (def (snapshot bound)
+ (assurance-snapshot "cut" rev "graph" (list (cons "source" rev))
+ (list (cons "claim" rev)) (string-append "events/" rev) "policy" "1"
+ (list artifact claim bound evidence decision) relations))
+ (let* ((bound (assurance-bind-obligation-to-snapshot obligation (snapshot obligation)))
+ (cut (snapshot bound))
+ (outcome (assurance-verifier-outcome "outcome" 'succeeded producer: "fixture"
+ tool: "local-verifier" tool-version: "1" obligation: "test"
+ subject: "release" scope: "repo" snapshot-revision: rev
+ snapshot-context-digest: (.ref cut 'context-digest)
+ inputs: (list (assurance-verifier-input artifact)) output-digest: digest-b)))
+ (let ((revision-value revision) (evidence-value evidence) (outcome-value outcome)
+ (claim-value claim) (decision-value decision))
+ (.o revision: revision-value snapshot: cut obligation: bound evidence: evidence-value
+ outcome: outcome-value claim: claim-value decision: decision-value)))))
+(def cuts (vector (lifecycle-cut 1) (lifecycle-cut 2)))
+(def (lifecycle-get fixture key) (hash-ref fixture key))
+(def (lifecycle-set! fixture key value) (hash-put! fixture key value))
+(def (increment! fixture key) (lifecycle-set! fixture key (+ 1 (lifecycle-get fixture key))))
+(def (make-lifecycle-fixture)
+ (let (fixture (make-hash-table-eq))
+ (for-each (lambda (key) (lifecycle-set! fixture key #f))
+ '(installed reported plan seal admission admission-seal preflight request grant reservation
+ started authorization-hook delivery-hook effect-clock-hook))
+ (for-each (lambda (key) (lifecycle-set! fixture key 0))
+ '(clock verifier-calls authorization-calls delivery-calls service-effects
+ grant-epoch grant-until reservation-revision reservation-epoch reservation-until delivery-status))
+ (lifecycle-set! fixture 'delivery-mode 'delivered)
+ (lifecycle-set! fixture 'policy-mode 'permit)
+ (lifecycle-set! fixture 'cut (vector-ref cuts 0))
+ (lifecycle-set! fixture 'verification-host
+ (assurance-verification-host "lifecycle/verification"
+ (lambda (subject now until)
+ (increment! fixture 'verifier-calls)
+ (lifecycle-get fixture 'installed))
+ (lambda () (lifecycle-get fixture 'clock)) 1
+ (lambda () (.ref (lifecycle-get fixture 'cut) 'snapshot))))
+ (lifecycle-set! fixture 'effect-host
+ (assurance-effect-host
+ (lambda (request)
+ (increment! fixture 'authorization-calls)
+ (when (lifecycle-get fixture 'authorization-hook)
+ ((lifecycle-get fixture 'authorization-hook)))
+ (poo-flow-cedar-decision "local-runtime-decision" (lifecycle-get fixture 'policy-mode)
+ "policy" digest-a digest-b '()))
+ (lambda (request token)
+ (increment! fixture 'delivery-calls)
+ (when (lifecycle-get fixture 'delivery-hook)
+ ((lifecycle-get fixture 'delivery-hook)))
+ (unless (eq? (lifecycle-get fixture 'delivery-mode) 'failed)
+ (increment! fixture 'service-effects))
+ (lifecycle-get fixture 'delivery-mode))
+ (lambda ()
+ (if (lifecycle-get fixture 'effect-clock-hook)
+ ((lifecycle-get fixture 'effect-clock-hook))
+ (lifecycle-get fixture 'clock))) 1))
+ fixture))
+
+(def (lifecycle-action! fixture name)
+ (let* ((cut (lifecycle-get fixture 'cut)) (vh (lifecycle-get fixture 'verification-host))
+ (rh (lifecycle-get fixture 'effect-host))
+ (snapshot (.ref cut 'snapshot)) (obligation (.ref cut 'obligation))
+ (evidence (.ref cut 'evidence)) (outcome (.ref cut 'outcome)))
+ (case name
+ ((init Terminal) #t)
+ ((InstallAdapter) (lifecycle-set! fixture 'installed #t) #t)
+ ((ReportSuccess) (lifecycle-set! fixture 'reported #t) #t)
+ ((Plan)
+ (let (plan (assurance-plan-verification "plan" snapshot '("source")
+ (assurance-verification-policy "policy" "1" '(gerbil-test))))
+ (unless (equal? (.ref plan 'selected-obligations) '("test"))
+ (error "fixture obligation was not selected"))
+ (lifecycle-set! fixture 'plan plan) #t))
+ ((Verify)
+ (and (lifecycle-get fixture 'plan)
+ (let (seal (assurance-host-verify vh snapshot obligation evidence outcome))
+ (lifecycle-set! fixture 'seal seal) (if seal #t #f))))
+ ((Admit UnsafeLateAdmit)
+ (let (admission (assurance-host-admit-verified vh obligation evidence outcome
+ (lifecycle-get fixture 'seal)))
+ (when admission
+ (lifecycle-set! fixture 'admission admission)
+ (lifecycle-set! fixture 'admission-seal (lifecycle-get fixture 'seal)))
+ (if admission #t #f)))
+ ((Decide)
+ (let (preflight (assurance-host-preflight-decision vh (.ref cut 'decision)
+ (.ref cut 'claim) (if (lifecycle-get fixture 'admission)
+ (list (lifecycle-get fixture 'admission)) '())))
+ (when preflight (lifecycle-set! fixture 'preflight preflight))
+ (if preflight #t #f)))
+ ((Authorize UnsafeEarlyGrant)
+ (let* ((request (assurance-host-cedar-request vh (lifecycle-get fixture 'preflight)
+ (string-copy "Release") (string-copy digest-a) (poo-flow-cedar-runtime-handoff
+ 1 #u8(1) digest-a digest-a digest-b digest-b)))
+ (grant (and request (assurance-effect-host-authorize! rh vh request))))
+ (when grant
+ (lifecycle-set! fixture 'request request)
+ (lifecycle-set! fixture 'grant grant)
+ (lifecycle-set! fixture 'grant-epoch (.ref grant 'epoch))
+ (lifecycle-set! fixture 'grant-until (.ref grant 'expires-at)))
+ (if grant #t #f)))
+ ((StartEffect UnsafeStart UnsafeAfterRevoke)
+ (let (reservation (assurance-effect-host-reserve! rh (lifecycle-get fixture 'grant)))
+ (when reservation
+ (lifecycle-set! fixture 'reservation reservation)
+ (lifecycle-set! fixture 'started #t)
+ (lifecycle-set! fixture 'reservation-revision (.ref cut 'revision))
+ (lifecycle-set! fixture 'reservation-epoch (lifecycle-get fixture 'grant-epoch))
+ (lifecycle-set! fixture 'reservation-until (lifecycle-get fixture 'grant-until)))
+ (if reservation #t #f)))
+ ((DeliverEffect RejectDelivery FailDelivery UnknownDelivery UnsafeDelivery UnsafeBeforeStart UnsafeReplay)
+ (lifecycle-set! fixture 'delivery-mode
+ (case name ((FailDelivery) 'failed) ((UnknownDelivery) 'indeterminate) (else 'delivered)))
+ (let* ((receipt (assurance-effect-host-deliver! rh (lifecycle-get fixture 'reservation)))
+ (status (case (.ref receipt 'outcome)
+ ((delivered) 1) ((rejected) 2) ((failed) 3) ((indeterminate) 4))))
+ ;; Unsafe probes must not rewrite an already attributable outcome.
+ (unless (memq name '(UnsafeDelivery UnsafeBeforeStart UnsafeReplay))
+ (lifecycle-set! fixture 'delivery-status status))
+ receipt))
+ ((Tick) (increment! fixture 'clock) #t)
+ ((ChangeSource)
+ (lifecycle-set! fixture 'cut (vector-ref cuts 1))
+ (for-each (lambda (key) (lifecycle-set! fixture key #f)) '(plan seal admission preflight request))
+ (lifecycle-set! fixture 'grant-until 0) #t)
+ ((Revoke)
+ (when (lifecycle-get fixture 'admission)
+ (assurance-host-revoke-admission! vh (lifecycle-get fixture 'admission)))
+ (when (and (lifecycle-get fixture 'seal)
+ (not (eq? (lifecycle-get fixture 'seal) (lifecycle-get fixture 'admission-seal))))
+ (assurance-host-revoke! vh (lifecycle-get fixture 'seal)))
+ (for-each (lambda (key) (lifecycle-set! fixture key #f)) '(seal admission preflight request))
+ (assurance-effect-host-revoke! rh) #t)
+ (else (error "unknown lifecycle action" name)))))
+
+(def (lifecycle-state fixture)
+ (let* ((cut (lifecycle-get fixture 'cut)) (rev (.ref cut 'revision))
+ (vh (lifecycle-get fixture 'verification-host)) (rh (lifecycle-get fixture 'effect-host))
+ (seal (lifecycle-get fixture 'seal)) (grant (lifecycle-get fixture 'grant)))
+ (.o revision: rev planned: (if (lifecycle-get fixture 'plan) rev 0)
+ verified: (if seal rev 0) sealUntil: (if seal (.ref seal 'expires-at) 0)
+ admitted: (if (assurance-host-admission-current? vh (lifecycle-get fixture 'admission)) rev 0)
+ decided: (if (assurance-host-decision-preflight-current? vh (lifecycle-get fixture 'preflight)) rev 0)
+ granted: (if (assurance-effect-host-grant-current? rh grant) rev 0)
+ grantEpoch: (lifecycle-get fixture 'grant-epoch) grantUntil: (lifecycle-get fixture 'grant-until)
+ epoch: (assurance-effect-host-epoch rh) clock: (lifecycle-get fixture 'clock)
+ adapterInstalled: (lifecycle-get fixture 'installed) reportedSuccess: (lifecycle-get fixture 'reported)
+ effectStarted: (lifecycle-get fixture 'started) startSafe: #t
+ reservationRevision: (lifecycle-get fixture 'reservation-revision)
+ reservationEpoch: (lifecycle-get fixture 'reservation-epoch)
+ reservationUntil: (lifecycle-get fixture 'reservation-until)
+ deliveryStatus: (lifecycle-get fixture 'delivery-status)
+ deliveryCalls: (lifecycle-get fixture 'delivery-calls) deliverySafe: #t)))
diff --git a/t/formal/lifecycle-replay-test.ss b/t/formal/lifecycle-replay-test.ss
new file mode 100644
index 0000000..840545b
--- /dev/null
+++ b/t/formal/lifecycle-replay-test.ss
@@ -0,0 +1,43 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+(import :std/test (only-in :clan/poo/object .ref)
+ ./lifecycle-fixture)
+(export lifecycle-replay-test)
+(def lifecycle-replay-test
+ (test-suite "Full bounded Quint lifecycle / real Scheme API graph replay"
+ (test-case "every supplied edge has the same projected Scheme state"
+ (let (file (getenv "AITIA_LIFECYCLE_REPLAY" #f))
+ (unless file (error "run through just check-lifecycle-scheme"))
+ (let (traces (call-with-input-file file read))
+ (check (pair? traces) => #t)
+ (for-each
+ (lambda (trace)
+ (let (fixture (make-lifecycle-fixture))
+ (for-each
+ (lambda (row)
+ (let ((action (car row)) (expected (cadr row)))
+ (if (memq action '(UnsafeStart UnsafeLateAdmit UnsafeEarlyGrant UnsafeAfterRevoke
+ UnsafeDelivery UnsafeBeforeStart UnsafeReplay))
+ (let* ((before (lifecycle-state fixture))
+ (calls (lifecycle-get fixture 'delivery-calls))
+ (result (lifecycle-action! fixture action)))
+ (if (memq action '(UnsafeDelivery UnsafeBeforeStart UnsafeReplay))
+ (check (.ref result 'outcome) => 'rejected)
+ (check result => #f))
+ (check (lifecycle-get fixture 'delivery-calls) => calls)
+ (let (after (lifecycle-state fixture))
+ (for-each (lambda (field)
+ (check (.ref after (car field)) => (.ref before (car field)))) expected)))
+ (begin
+ (let (result (lifecycle-action! fixture action))
+ (check (if result #t #f) => #t))
+ (let (actual (lifecycle-state fixture))
+ (for-each (lambda (field)
+ (check (.ref actual (car field)) => (cadr field))) expected))))
+ (when (eq? action 'StartEffect)
+ (check (lifecycle-get fixture 'delivery-calls) => 0)
+ (check (lifecycle-get fixture 'service-effects) => 0))))
+ trace)
+ (displayln "LIFECYCLE-TRACE-COMPLETE steps=" (length trace))
+ (force-output))) traces))))))
diff --git a/t/formal/retirement-binding-test.ss b/t/formal/retirement-binding-test.ss
new file mode 100644
index 0000000..8a6bce1
--- /dev/null
+++ b/t/formal/retirement-binding-test.ss
@@ -0,0 +1,91 @@
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+(import :std/test
+ (only-in :std/misc/ports read-all-as-string)
+ (only-in :clan/poo/object .ref)
+ (only-in :poo-flow/src/feature-system/source-lock-feature source-lock-payload-digest)
+ :poo-flow/lambda-aitia/modules/assurance/interface
+ :poo-flow/lambda-aitia/modules/sdlc/interface
+ :poo-flow/lambda-aitia/user-interface/scenarios/job-retirement-contract)
+(export retirement-binding-test)
+
+;;; IO belongs to the qualification fixture, not the public design value.
+;;; Hash every member before making one group artifact: changing any model,
+;;; config, imported helper or proof source invalidates the declared source cut.
+(def (source-group-digest paths)
+ (assurance-canonical-digest
+ (map (lambda (path)
+ (list path (source-lock-payload-digest
+ (call-with-input-file path read-all-as-string)))) paths)))
+(def (source id digest)
+ (assurance-artifact id "r1" 'supported digest
+ owner: "lambda-aitia" media-kind: 'formal-source
+ provenance: '("repository-source-bytes")))
+(def quint-source (source "retirement/quint" (source-group-digest job-retirement-quint-sources)))
+(def lean-source (source "retirement/lean" (source-group-digest job-retirement-lean-sources)))
+(def scheme-source (source "retirement/scheme" (source-group-digest job-retirement-scheme-sources)))
+(def (snapshot quint lean scheme)
+ (assurance-snapshot
+ "retirement/formal-cut" "cut-1" "retirement/formal-sources"
+ '(("retirement/quint" . "r1") ("retirement/lean" . "r1")
+ ("retirement/scheme" . "r1")) '()
+ "retirement/design-r1" "formal-review" "r1" (list quint lean scheme) '()))
+(def source-cut (snapshot quint-source lean-source scheme-source))
+(def links
+ (map (lambda (artifact)
+ (sdlc-design-implementation-link
+ job-retirement-contract job-retirement-safety artifact source-cut))
+ (list quint-source lean-source scheme-source)))
+
+(def retirement-binding-test
+ (test-suite "Retirement POO contract to formal source binding"
+ (test-case "three source cuts bind one declaration without granting conformance"
+ (let (review (sdlc-design-implementation-review
+ job-retirement-contract source-cut links))
+ (check (.ref review 'mapping-current?) => #t)
+ (for-each
+ (lambda (link)
+ (check (.ref link 'contract) => "job-executor/retirement")
+ (check (.ref link 'contract-revision) => "r1")
+ (check (.ref link 'contract-digest)
+ => (sdlc-design-contract-digest job-retirement-contract))) links)
+ (check (.ref review 'implementation-conforms?) => #f)
+ (check (.ref review 'evidence-admitted?) => #f)
+ (check (.ref review 'release-authorized?) => #f)))
+ (test-case "same-revision Quint source mutation invalidates the joined mapping"
+ (let* ((changed (source "retirement/quint" (source-lock-payload-digest "changed model")))
+ (review (sdlc-design-implementation-review
+ job-retirement-contract (snapshot changed lean-source scheme-source) links)))
+ (check (.ref review 'mapping-current?) => #f)
+ (check (length (.ref review 'blocked-links)) => 3)
+ (check (.ref (car (.ref review 'blocked-links)) 'blocker) => 'snapshot-changed)))
+ (test-case "same-revision Lean source mutation also invalidates the mapping"
+ (let* ((changed (source "retirement/lean" (source-lock-payload-digest "changed law")))
+ (review (sdlc-design-implementation-review
+ job-retirement-contract (snapshot quint-source changed scheme-source) links)))
+ (check (.ref review 'mapping-current?) => #f)
+ (check (length (.ref review 'blocked-links)) => 3)
+ (check (.ref (car (.ref review 'blocked-links)) 'blocker) => 'snapshot-changed)))
+ (test-case "same-revision Scheme implementation mutation invalidates the mapping"
+ (let* ((changed (source "retirement/scheme" (source-lock-payload-digest "changed Host")))
+ (review (sdlc-design-implementation-review
+ job-retirement-contract (snapshot quint-source lean-source changed) links)))
+ (check (.ref review 'mapping-current?) => #f)
+ (check (length (.ref review 'blocked-links)) => 3)
+ (check (.ref (car (.ref review 'blocked-links)) 'blocker) => 'snapshot-changed)))
+ (test-case "a changed design premise cannot reuse either formal link"
+ (let* ((changed
+ (sdlc-design-contract
+ "job-executor/retirement" "r1" "RFC/0004/retirement-ingress"
+ (.ref job-retirement-contract 'responsibilities)
+ (.ref job-retirement-contract 'owned-state) '()
+ (cons (sdlc-design-assumption "distributed-fence" "a1"
+ "Closing and epoch advancement may be separate operations.")
+ (.ref job-retirement-contract 'assumptions))
+ (list job-retirement-safety) '()))
+ (review (sdlc-design-implementation-review changed source-cut links)))
+ (check (.ref review 'mapping-current?) => #f)
+ (check (length (.ref review 'blocked-links)) => 3)
+ (for-each (lambda (link) (check (.ref link 'blocker) => 'contract-changed))
+ (.ref review 'blocked-links))))))
diff --git a/t/formal/retirement-execution-test.ss b/t/formal/retirement-execution-test.ss
new file mode 100644
index 0000000..72710a5
--- /dev/null
+++ b/t/formal/retirement-execution-test.ss
@@ -0,0 +1,157 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; Execute the bounded retirement-ingress controls against the local Scheme
+;;; Host. The callback counter is an actual handoff result, not model metadata.
+(import :std/test
+ (only-in :clan/poo/object .ref)
+ :poo-flow/lambda-aitia/modules/sdlc/retirement-host)
+(export retirement-execution-test)
+
+(def (accepted? result) (.ref result 'accepted?))
+(def (reason result) (.ref result 'reason))
+(def (ready-for-review host)
+ (aitia-retirement-host-observe-completion! host)
+ (aitia-retirement-host-observe-queue-drained! host)
+ (aitia-retirement-host-complete-inventory! host)
+ host)
+
+(def retirement-execution-test
+ (test-suite "Retirement ingress actual Scheme Host execution"
+ (test-case "cancellation is not completion or queue drain"
+ (let (host (aitia-retirement-host))
+ (check (accepted? (aitia-retirement-host-request-cancellation! host)) => #t)
+ (aitia-retirement-host-complete-inventory! host)
+ (aitia-retirement-host-close-ingress! host)
+ (check (reason (aitia-retirement-host-retire! host)) => 'active-work)
+ (check (.ref (aitia-retirement-host-snapshot host) 'retired?) => #f)))
+ (test-case "unfenced review cannot retire"
+ (let (host (ready-for-review (aitia-retirement-host)))
+ (check (accepted? (aitia-retirement-host-review-cut! host)) => #t)
+ (check (reason (aitia-retirement-host-retire! host)) => 'ingress-open)
+ (check (.ref (aitia-retirement-host-snapshot host) 'authority-epoch) => 0)
+ (check (accepted? (aitia-retirement-host-close-ingress! host)) => #t)
+ (check (.ref (aitia-retirement-host-snapshot host) 'authority-epoch) => 1)
+ (check (accepted? (aitia-retirement-host-retire! host)) => #t)))
+ (test-case "cancellation does not discharge queued work after completion"
+ (let (host (aitia-retirement-host))
+ (aitia-retirement-host-request-cancellation! host)
+ (aitia-retirement-host-observe-completion! host)
+ (aitia-retirement-host-complete-inventory! host)
+ (aitia-retirement-host-close-ingress! host)
+ (check (reason (aitia-retirement-host-review-cut! host)) => 'queued-work)
+ (check (reason (aitia-retirement-host-retire! host)) => 'queued-work)
+ (check (.ref (aitia-retirement-host-snapshot host) 'queued?) => #t)))
+ (test-case "quiescence inventory and fence cannot substitute for review"
+ (let (host (ready-for-review (aitia-retirement-host)))
+ (aitia-retirement-host-close-ingress! host)
+ (check (reason (aitia-retirement-host-retire! host)) => 'review-missing)
+ (check (accepted? (aitia-retirement-host-review-cut! host)) => #t)
+ (check (accepted? (aitia-retirement-host-retire! host)) => #t)))
+ (test-case "closure is idempotent and fences an already pending request"
+ (let* ((host (aitia-retirement-host)) (calls 0)
+ (handoff (aitia-retirement-effect-handoff
+ (lambda () (set! calls (+ calls 1))))))
+ (aitia-retirement-host-request-start! host)
+ (aitia-retirement-host-close-ingress! host)
+ (check (reason (aitia-retirement-host-close-ingress! host)) => 'already-closed)
+ (check (.ref (aitia-retirement-host-snapshot host) 'authority-epoch) => 1)
+ (check (reason (aitia-retirement-host-attempt-start! host handoff)) => 'stale-grant)
+ (check calls => 0)
+ (check (.ref (aitia-retirement-host-snapshot host) 'attempted?) => #t)))
+ (test-case "failed handoff releases the transient fence without retry or completion"
+ (let* ((host (ready-for-review (aitia-retirement-host))) (calls 0)
+ (handoff (aitia-retirement-effect-handoff
+ (lambda () (set! calls (+ calls 1)) (error "handoff failed")))))
+ (aitia-retirement-host-review-cut! host)
+ (aitia-retirement-host-request-start! host)
+ (check (with-catch (lambda (_failure) 'failed)
+ (lambda () (aitia-retirement-host-attempt-start! host handoff)
+ 'unexpected-success)) => 'failed)
+ (check calls => 1)
+ (check (.ref (aitia-retirement-host-snapshot host) 'handoff-in-progress?) => #f)
+ (check (.ref (aitia-retirement-host-snapshot host) 'active?) => #t)
+ (check (accepted? (aitia-retirement-host-request-start! host)) => #f)
+ (check (reason (aitia-retirement-host-retire! host)) => 'active-work)
+ (check (accepted? (aitia-retirement-host-observe-completion! host)) => #t)
+ (aitia-retirement-host-close-ingress! host)
+ (check (accepted? (aitia-retirement-host-retire! host)) => #t)))
+ (test-case "late work invalidates the old finalization attempt"
+ (let (host (ready-for-review (aitia-retirement-host)))
+ (aitia-retirement-host-review-cut! host)
+ (aitia-retirement-host-observe-late-activity! host)
+ (aitia-retirement-host-close-ingress! host)
+ (check (reason (aitia-retirement-host-retire! host)) => 'active-work)
+ (check (.ref (aitia-retirement-host-snapshot host) 'retired?) => #f)))
+ (test-case "unknown inventory prevents retirement after review"
+ (let (host (ready-for-review (aitia-retirement-host)))
+ (aitia-retirement-host-review-cut! host)
+ (aitia-retirement-host-observe-inventory-gap! host)
+ (aitia-retirement-host-close-ingress! host)
+ (check (reason (aitia-retirement-host-retire! host)) => 'inventory-unknown)
+ (check (.ref (aitia-retirement-host-snapshot host) 'retired?) => #f)))
+ (test-case "accepted current grant invokes one real Scheme handoff"
+ (let* ((host (aitia-retirement-host))
+ (calls 0)
+ (reentered #f)
+ (handoff (aitia-retirement-effect-handoff
+ (lambda ()
+ (set! calls (+ calls 1))
+ (set! reentered
+ (accepted? (aitia-retirement-host-request-start! host)))))))
+ (check (accepted? (aitia-retirement-host-request-start! host)) => #t)
+ (let (result (aitia-retirement-host-attempt-start! host handoff))
+ (check (accepted? result) => #t)
+ (check (.ref result 'handoff-executed?) => #t))
+ (check calls => 1)
+ (check reentered => #f)
+ (check (accepted? (aitia-retirement-host-request-start! host)) => #f)
+ (check (reason (aitia-retirement-host-retire! host)) => 'active-work)))
+ (test-case "foreign thread cannot use the serialized Host"
+ (let* ((host (aitia-retirement-host))
+ (result
+ (thread-join!
+ (thread-start!
+ (make-thread
+ (lambda ()
+ (with-catch (lambda (_failure) 'refused)
+ (lambda ()
+ (aitia-retirement-host-close-ingress! host)
+ 'accepted))))))))
+ (check result => 'refused)
+ (check (.ref (aitia-retirement-host-snapshot host) 'ingress-open?) => #t)))
+ (test-case "a reentrant handoff cannot retire before it returns"
+ (let* ((host (ready-for-review (aitia-retirement-host)))
+ (premature 'not-attempted)
+ (handoff
+ (aitia-retirement-effect-handoff
+ (lambda ()
+ (set! premature
+ (with-catch (lambda (_failure) 'refused)
+ (lambda ()
+ (aitia-retirement-host-observe-completion! host)
+ (aitia-retirement-host-close-ingress! host)
+ (accepted? (aitia-retirement-host-retire! host)))))))))
+ (aitia-retirement-host-review-cut! host)
+ (aitia-retirement-host-request-start! host)
+ (check (accepted? (aitia-retirement-host-attempt-start! host handoff)) => #t)
+ (check premature => 'refused)
+ (check (.ref (aitia-retirement-host-snapshot host) 'active?) => #t)
+ (check (.ref (aitia-retirement-host-snapshot host) 'retired?) => #f)))
+ (test-case "post-retirement cached grant cannot execute the handoff"
+ (let* ((host (ready-for-review (aitia-retirement-host)))
+ (calls 0)
+ (handoff (aitia-retirement-effect-handoff
+ (lambda () (set! calls (+ calls 1))))))
+ (aitia-retirement-host-review-cut! host)
+ (aitia-retirement-host-close-ingress! host)
+ (check (accepted? (aitia-retirement-host-retire! host)) => #t)
+ (check (accepted? (aitia-retirement-host-request-start! host)) => #t)
+ (let (result (aitia-retirement-host-attempt-start! host handoff))
+ (check (accepted? result) => #f)
+ (check (reason result) => 'stale-grant)
+ (check (.ref result 'handoff-executed?) => #f))
+ (check calls => 0)
+ (check (.ref (aitia-retirement-host-snapshot host) 'active?) => #f)
+ (check (.ref (aitia-retirement-host-snapshot host) 'retired?) => #t)))))
diff --git a/t/formal/retirement-replay-test.ss b/t/formal/retirement-replay-test.ss
new file mode 100644
index 0000000..11b711e
--- /dev/null
+++ b/t/formal/retirement-replay-test.ss
@@ -0,0 +1,85 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; ITF is projected to data, never evaluated. The native test lifecycle owns
+;;; execution; the Quint runner only supplies model states and action names.
+(import :std/test
+ (only-in :clan/poo/object .ref)
+ :poo-flow/lambda-aitia/modules/sdlc/retirement-host)
+(export retirement-replay-test)
+
+(def (invoke-action name host handoff)
+ (case name
+ ((init Terminal) #f)
+ ((RequestCancellation) (aitia-retirement-host-request-cancellation! host))
+ ((ObserveCompletion) (aitia-retirement-host-observe-completion! host))
+ ((ObserveQueueDrained) (aitia-retirement-host-observe-queue-drained! host))
+ ((CompleteInventory) (aitia-retirement-host-complete-inventory! host))
+ ((ReviewCut) (aitia-retirement-host-review-cut! host))
+ ((ObserveLateActivity) (aitia-retirement-host-observe-late-activity! host))
+ ((ObserveInventoryGap) (aitia-retirement-host-observe-inventory-gap! host))
+ ((CloseIngress) (aitia-retirement-host-close-ingress! host))
+ ((Request) (aitia-retirement-host-request-start! host))
+ ((Start Reject TrustCachedGrant) (aitia-retirement-host-attempt-start! host handoff))
+ ((Retire RetireWithoutFence) (aitia-retirement-host-retire! host))
+ (else (error "unknown replay action" name))))
+
+(def (check-state host expected)
+ (let (snapshot (aitia-retirement-host-snapshot host))
+ (for-each (lambda (field)
+ (check (.ref snapshot (car field)) => (cadr field))) expected)
+ (check (.ref snapshot 'handoff-in-progress?) => #f)))
+
+(def (replay lane trace)
+ (let* ((host (aitia-retirement-host))
+ (calls 0)
+ (expected-calls 0)
+ (handoff (aitia-retirement-effect-handoff
+ (lambda () (set! calls (+ calls 1))))))
+ (let loop ((states trace))
+ (unless (null? states)
+ (let* ((row (car states)) (name (car row))
+ (before (aitia-retirement-host-snapshot host))
+ (result (invoke-action name host handoff))
+ (fault? (and (not (eq? lane 'safe)) (null? (cdr states)))))
+ (cond
+ (fault?
+ (check (memq name '(RetireWithoutFence TrustCachedGrant)) ? pair?)
+ (check (.ref result 'accepted?) => #f)
+ (check (.ref result 'handoff-executed?) => #f)
+ (check (.ref result 'reason)
+ => (if (eq? name 'RetireWithoutFence) 'ingress-open 'stale-grant))
+ ;; The intentionally unsafe model's final state must NOT match
+ ;; Scheme. Compare the unchanged cut, except consumed start flags.
+ (for-each
+ (lambda (slot)
+ (check (.ref (aitia-retirement-host-snapshot host) slot)
+ => (.ref before slot)))
+ '(active? queued? retired? ingress-open? authority-epoch))
+ (when (eq? name 'TrustCachedGrant)
+ (check (.ref (aitia-retirement-host-snapshot host) 'pending?) => #f)
+ (check (.ref (aitia-retirement-host-snapshot host) 'attempted?) => #t)))
+ (else
+ (when result
+ (check (.ref result 'accepted?) => (not (eq? name 'Reject)))
+ (check (.ref result 'handoff-executed?)
+ => (if (memq name '(Start TrustCachedGrant)) #t #f)))
+ (when (memq name '(Start TrustCachedGrant))
+ (set! expected-calls (+ expected-calls 1)))
+ (check-state host (cadr row))))
+ (check calls => expected-calls)
+ (loop (cdr states)))))))
+
+(def retirement-replay-test
+ (test-suite "Quint MBT / real Scheme Host correspondence"
+ (test-case "safe state traces and typed unsafe-model refusals"
+ (let (fixture (getenv "AITIA_QUINT_REPLAY" #f))
+ (unless fixture (error "run via just check-quint-scheme; model traces are required"))
+ (let (lanes (call-with-input-file fixture read))
+ (check (map car lanes) => '(safe unfenced cached))
+ (for-each
+ (lambda (lane)
+ (check (pair? (cadr lane)) => #t)
+ (for-each (lambda (trace) (replay (car lane) trace)) (cadr lane)))
+ lanes))))))
diff --git a/t/gitops/unit/gitops-protocol-test.ss b/t/gitops/unit/gitops-protocol-test.ss
index 83de2e1..4694f80 100644
--- a/t/gitops/unit/gitops-protocol-test.ss
+++ b/t/gitops/unit/gitops-protocol-test.ss
@@ -5,7 +5,7 @@
(import :std/test
(only-in :clan/poo/object .cc .ref)
(only-in :clan/poo/mop element?)
- :poo-flow/src/module-system/poo-clos/interface
+ :poo-flow/core/poo-clos/interface
:poo-flow/lambda-aitia/modules/gitops/interface)
(export gitops-protocol-test)
diff --git a/t/integration/github-workflow-test.ss b/t/integration/github-workflow-test.ss
index 4c2da9e..f46c69e 100644
--- a/t/integration/github-workflow-test.ss
+++ b/t/integration/github-workflow-test.ss
@@ -3,31 +3,55 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import :std/test
+ (only-in :std/list/list find)
(only-in :clan/poo/object .ref)
- :poo-flow/src/module-system/profile-composition/interface
:poo-flow/lambda-aitia/user-interface/config
+ :poo-flow/lambda-aitia/user-interface/profiles/nasa/sdlc
:poo-flow/lambda-aitia/user-interface/scenarios/github-gitops)
(export github-workflow-test)
(def repository "tao3k/poo-flow")
(def revision "0123456789abcdef")
+(def nasa-standard (car (.ref nasa-sdlc-npr7150_2 'standards)))
+(def (nasa-check (edition (.ref nasa-standard 'edition))
+ (digest (.ref nasa-standard 'source-lock-digest)))
+ (github-check 'nasa/sdlc/npr-7150.2 repository revision 'success
+ standard-edition: edition source-lock-digest: digest))
(def (successful-checks names (at-revision revision))
- (map (lambda (name) (github-check name repository at-revision 'success)) names))
-(def dev-checks '(commit-policy build unit-test nasa-7150-2d))
+ (map (lambda (name)
+ (if (eq? name 'nasa/sdlc/npr-7150.2)
+ (github-check name repository at-revision 'success
+ standard-edition: (.ref nasa-standard 'edition)
+ source-lock-digest:
+ (.ref nasa-standard 'source-lock-digest))
+ (github-check name repository at-revision 'success)))
+ names))
+(def dev-checks '(commit-policy build unit-test nasa/sdlc/npr-7150.2))
(def (run-github-gitops change checks)
(github-run-gitops github-gitops-sdlc change checks))
(def github-workflow-test
(test-suite "GitHub GitOps SDLC lifecycle"
(test-case "Dev Staging Production are GitOps Profiles; NASA is one Standard"
- (check-equal? (poo-flow-scenario-case-name github-gitops-sdlc)
+ (check-equal? (.ref github-gitops-sdlc 'name)
'github-gitops-sdlc)
(check-equal? (map (lambda (profile) (.ref profile 'name))
- (poo-flow-scenario-case-profiles github-gitops-sdlc))
- '(actions dev staging production nasa-7150-2d))
+ (.ref github-gitops-sdlc 'profiles))
+ '(actions dev staging production nasa/sdlc/npr-7150.2))
(check-equal? (map (lambda (standard) (.ref standard 'identity))
- (.ref nasa-7150-2d 'standards))
- '("nasa/npr-7150.2d")))
+ (.ref nasa-sdlc-npr7150_2 'standards))
+ '("nasa/sdlc/npr-7150.2"))
+ (check-equal? (map (lambda (standard) (.ref standard 'edition))
+ (.ref nasa-sdlc-npr7150_2 'standards))
+ '("D"))
+ (let* ((proof
+ (find (lambda (value)
+ (eq? (.ref value 'profile-identity) 'npr-7150.2))
+ (.ref github-gitops-sdlc 'selection-proofs)))
+ (module-id (.ref proof 'module-definition)))
+ (check-equal? (.ref module-id 'namespace) 'nasa)
+ (check-equal? (.ref module-id 'name) 'sdlc)
+ (check-equal? (.ref proof 'profile-identity) 'npr-7150.2)))
(test-case "PR into develop advances Dev only with current checks and Standard assessment"
(let* ((change (github-change 'pull-request repository revision
"feature/gitops" "develop" 42))
@@ -36,7 +60,7 @@
(check-equal? (.ref decision 'environment) 'dev)
(check-equal? (.ref decision 'accepted) #t)
(check-equal? (.ref decision 'next-profile) 'staging)
- (check-equal? (.ref decision 'standards) '(nasa-7150-2d))))
+ (check-equal? (.ref decision 'standards) '(nasa/sdlc/npr-7150.2))))
(test-case "missing NASA assessment fails closed"
(let (decision
(run-github-gitops
@@ -44,7 +68,34 @@
"feature/gitops" "develop" 42)
(successful-checks '(commit-policy build unit-test))))
(check-equal? (.ref decision 'accepted) #f)
- (check-equal? (.ref decision 'missing-checks) '(nasa-7150-2d))))
+ (check-equal? (.ref decision 'missing-checks) '(nasa/sdlc/npr-7150.2))))
+ (test-case "same-name green check without an edition-bound source lock fails closed"
+ (let (decision
+ (run-github-gitops
+ (github-change 'pull-request repository revision
+ "feature/gitops" "develop" 42)
+ (append (successful-checks '(commit-policy build unit-test))
+ (list (github-check 'nasa/sdlc/npr-7150.2
+ repository revision 'success)))))
+ (check-equal? (.ref decision 'accepted) #f)
+ (check-equal? (.ref decision 'stale-checks) '(nasa/sdlc/npr-7150.2))
+ (check-equal? (.ref decision 'reasons)
+ '(standard-assessment-mismatch))))
+ (test-case "wrong NASA edition or source lock cannot authorize promotion"
+ (for-each
+ (lambda (check)
+ (let (decision
+ (run-github-gitops
+ (github-change 'pull-request repository revision
+ "feature/gitops" "develop" 42)
+ (append (successful-checks '(commit-policy build unit-test))
+ (list check))))
+ (check-equal? (.ref decision 'accepted) #f)
+ (check-equal? (.ref decision 'reasons)
+ '(standard-assessment-mismatch))))
+ (list (nasa-check "C") (nasa-check
+ (.ref nasa-standard 'edition)
+ "sha256:wrong-lock"))))
(test-case "checks for another Git revision cannot authorize promotion"
(let (decision
(run-github-gitops
@@ -58,13 +109,13 @@
(run-github-gitops
(github-change 'push repository revision "develop" "develop" #f)
(successful-checks
- '(integration-test security-review nasa-7150-2d))))
+ '(integration-test security-review nasa/sdlc/npr-7150.2))))
(production-decision
(run-github-gitops
(github-change 'workflow-dispatch repository revision
"develop" "production" #f)
(successful-checks
- '(release-authority signed-provenance nasa-7150-2d)))))
+ '(release-authority signed-provenance nasa/sdlc/npr-7150.2)))))
(check-equal? (.ref staging-decision 'profile) 'staging)
(check-equal? (.ref staging-decision 'next-profile) 'production)
(check-equal? (.ref production-decision 'profile) 'production)
diff --git a/t/modules/unit/composition-identity-test.ss b/t/modules/unit/composition-identity-test.ss
index 01d81ef..e5ceea2 100644
--- a/t/modules/unit/composition-identity-test.ss
+++ b/t/modules/unit/composition-identity-test.ss
@@ -5,19 +5,15 @@
(import (only-in :std/test check-equal? test-case test-suite)
(only-in :clan/poo/object .ref)
- (only-in :poo-flow/src/module-system/profile-composition/interface
- poo-flow-composition-catalog-ref
- poo-flow-composition-identity-official?
- poo-flow-composition-identity-qualified-name
- poo-flow-load-composition-value
+ (only-in :poo-flow/src/scenario/case
poo-flow-scenario-case?)
- (only-in :poo-flow/src/module-system/declaration/interface
+ (only-in :poo-flow/src/user-interface/module-selection
poo-flow-user-module-selection-flags
poo-flow-user-module-selection-key)
- (only-in :poo-flow/src/modules/proof/interface
- poo-flow-proof-module?)
(only-in :poo-flow/src/user-interface/profile-core
poo-flow-user-profile-modules)
+ (only-in :poo-flow/modules/proof/interface
+ poo-flow-proof-module?)
:poo-flow/lambda-aitia/modules/formal-methods/interface
:poo-flow/lambda-aitia/user-interface/compositions/aitia)
@@ -25,58 +21,36 @@
(def lambda-aitia-composition-identity-test
(test-suite
- "official Aitia Composition identity"
- (test-case "publishes Aitia through the reserved unqualified selector"
- (check-equal?
- (poo-flow-composition-identity-qualified-name
- aitia-composition-identity)
- 'aitia)
- (check-equal?
- (poo-flow-composition-identity-official?
- aitia-composition-identity)
- #t))
- (test-case "registers the official Aitia value without namespace fallback"
- (check-equal?
- (eq? (poo-flow-composition-catalog-ref
- aitia-composition-catalog 'aitia)
- aitia-composition)
- #t)
- (check-equal?
- (.ref aitia-composition-catalog 'module-names)
- '(gitops sdlc ADR assurance formal-methods)))
- (test-case "default Profile selects the complete software engineering closure"
+ "official Aitia native Profile composition"
+ (test-case "Aitia owns one qualified semantic Module"
+ (check-equal? (.ref (.ref AitiaModule 'identity) 'namespace)
+ 'software-engineering)
+ (check-equal? (.ref (.ref AitiaModule 'identity) 'name) 'aitia))
+ (test-case "root is one closed POO Flow Case"
+ (check-equal? (poo-flow-scenario-case? aitia) #t)
+ (check-equal? (.ref aitia 'name) 'aitia)
+ (check-equal? (length (.ref aitia 'profiles)) 1)
+ (check-equal? (length (.ref aitia 'profile-bindings)) 1)
+ (check-equal? (length (.ref aitia 'selection-proofs)) 1)
+ (check-equal? (.ref (.ref aitia 'admission) 'accepted?) #t)
+ (check-equal? (.ref aitia 'runtime-executed?) #f))
+ (test-case "default Project selects the complete software engineering closure"
(let (modules
- (poo-flow-user-profile-modules
- (.ref aitia-composition 'default-profile)))
+ (poo-flow-user-profile-modules (car (.ref aitia 'profiles))))
(check-equal?
(map poo-flow-user-module-selection-key modules)
'((software-engineering . gitops)
(software-engineering . sdlc)
+ (nasa . sdlc)
(software-engineering . ADR)
(software-engineering . assurance)
(software-engineering . formal-methods)))
(check-equal?
- (poo-flow-user-module-selection-flags (list-ref modules 4))
- '(+tla+ +lean +cedar))))
+ (poo-flow-user-module-selection-flags (list-ref modules 5))
+ '(+quint +lean +cedar))))
(test-case "formal-method declarations reuse the POO Flow Proof module"
(check-equal? (poo-flow-proof-module? aitia-formal-methods-module) #t)
- (check-equal? (.ref aitia-tla-plus-provider 'runtime-executed?) #f)
+ (check-equal? (.ref aitia-quint-provider 'runtime-executed?) #f)
(check-equal? (.ref aitia-lean-provider 'obligation-only?) #t)
(check-equal? (.ref aitia-cedar-provider 'role)
- 'authorization-policy-validation))
- (test-case "loads the downstream file as one closed Case"
- (let (case-value
- (poo-flow-load-composition-value
- "t/fixtures/composition-value/aitia.ss"
- aitia-composition-catalog))
- (check-equal? (poo-flow-scenario-case? case-value) #t)
- (check-equal? (.ref case-value 'name) 'aitia)
- (check-equal? (length (.ref case-value 'profiles)) 1)
- (check-equal? (length (.ref case-value 'stages)) 1)
- (check-equal?
- (.ref (.ref (car (.ref case-value 'profiles)) 'project) 'repository)
- "tao3k/lambda-aitia")
- (check-equal?
- (.ref (.ref (car (.ref case-value 'profiles)) 'delivery) 'provider)
- 'github)
- (check-equal? (.ref (.ref case-value 'admission) 'accepted?) #t)))))
+ 'authorization-policy-validation))))
diff --git a/t/modules/unit/source-collection-test.ss b/t/modules/unit/source-collection-test.ss
index 87094b8..96177ae 100644
--- a/t/modules/unit/source-collection-test.ss
+++ b/t/modules/unit/source-collection-test.ss
@@ -3,10 +3,10 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import (only-in :std/test check-equal? test-case test-suite)
- (only-in :poo-flow/src/module-system/loader/source
+ (only-in :poo-flow/core/module-system/source/objects
poo-flow-module-source-ref-metadata
poo-flow-module-source-ref-value)
- (only-in :poo-flow/src/module-system/loader/module-source-interface
+ (only-in :poo-flow/src/authoring/module-source-collection
make-poo-flow-contribution-module-source
poo-flow-load-modules))
diff --git a/t/sdlc/source-admission-test.ss b/t/sdlc/source-admission-test.ss
index c41f7b9..3d503cf 100644
--- a/t/sdlc/source-admission-test.ss
+++ b/t/sdlc/source-admission-test.ss
@@ -16,7 +16,7 @@
(only-in :asp-gerbil-scheme/testing-source-admission-api
testing-interface-prepared-source-admission-suite)
(only-in :std/test check)
- (only-in :poo-flow/src/module-system/observability/source-admission
+ (only-in :poo-flow/src/testing/source-admission
poo-flow-source-admission-observability-profile-prototype
poo-flow-observe-source-admission!
poo-flow-source-admission)
diff --git a/t/sdlc/unit/design-contract-test.ss b/t/sdlc/unit/design-contract-test.ss
new file mode 100644
index 0000000..c4eeefb
--- /dev/null
+++ b/t/sdlc/unit/design-contract-test.ss
@@ -0,0 +1,179 @@
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+(import :std/test :std/error
+ (only-in :clan/poo/object .ref)
+ :poo-flow/lambda-aitia/modules/sdlc/interface)
+
+(export design-contract-test)
+
+(def idempotency
+ (sdlc-design-assumption
+ "external-idempotency" "a1"
+ "The provider deduplicates tenant/job/operation keys"))
+(def unique-effect
+ (sdlc-design-guarantee
+ "unique-effect" "g1"
+ "At most one externally committed effect per operation key"))
+(def cancellation
+ (sdlc-design-guarantee
+ "cancel-before-commit" "g1"
+ "Cancellation before the commit point prevents a new commit"))
+(def retry-v1
+ (sdlc-design-variation "retry-policy" "v1" '("unique-effect")))
+(def retry-v2
+ (sdlc-design-variation "retry-policy" "v2" '("unique-effect")))
+
+(def (contract revision assumptions guarantees variations
+ effects: (effects '("external-operation"))
+ owned-state: (owned '("job-ledger"))
+ decision: (decision "ADR/job-executor"))
+ (sdlc-design-contract
+ "job-executor" revision decision '("execute-job") owned effects
+ assumptions guarantees variations))
+
+(def baseline
+ (contract "r1" (list idempotency) (list unique-effect cancellation)
+ (list retry-v1)))
+
+(def design-contract-test
+ (test-suite "SDLC design contract replacement"
+ (test-case "retry variation reopens only its declared guarantee"
+ (let* ((next (contract "r2" (list idempotency)
+ (list unique-effect cancellation) (list retry-v2)))
+ (review (sdlc-design-replacement-review baseline next))
+ (project (sdlc-project "job-service" "r2" "software" #t))
+ (retry-node (sdlc-trace-node "retry-policy" 'design project))
+ (guarantee-node (sdlc-trace-node "unique-effect" 'design project))
+ (verification-node
+ (sdlc-trace-node "verify-unique-effect" 'verification project))
+ (edges
+ (list (sdlc-trace-edge "retry-to-guarantee" "design-depends-on"
+ "retry-policy" "unique-effect" project)
+ (sdlc-trace-edge "guarantee-to-check" "design-depends-on"
+ "unique-effect" "verify-unique-effect" project)))
+ (impact
+ (sdlc-change-impact project
+ (list retry-node guarantee-node verification-node)
+ edges (.ref review 'changed-identities)
+ '("design-depends-on"))))
+ (check-equal? (.ref review 'declared-substitutable?) #f)
+ (check-equal? (.ref review 'blockers) '(variation-changed))
+ (check-equal? (.ref review 'changed-identities) '("retry-policy"))
+ (check-equal? (.ref review 'recheck-guarantees) '("unique-effect"))
+ (check-equal? (.ref impact 'recheck-verifications)
+ '("verify-unique-effect"))
+ (check-equal? (.ref review 'evidence-admitted?) #f)
+ (check-equal? (.ref review 'release-authorized?) #f)))
+ (test-case "new provider assumption cannot masquerade as compatible"
+ (let* ((extra (sdlc-design-assumption
+ "provider-available" "a1" "Provider is always available"))
+ (next (contract "r2" (list idempotency extra)
+ (list unique-effect cancellation) (list retry-v1)))
+ (review (sdlc-design-replacement-review baseline next)))
+ (check-equal? (.ref review 'blockers)
+ '(stronger-or-changed-assumption))
+ (check-equal? (.ref review 'recheck-guarantees)
+ '("cancel-before-commit" "unique-effect"))))
+ (test-case "a weakened guarantee or added effect is an explicit blocker"
+ (let* ((weakened-effect
+ (sdlc-design-guarantee
+ "unique-effect" "g2"
+ "At most two externally committed effects per operation key"))
+ (weakened (contract "r2" (list idempotency)
+ (list weakened-effect cancellation)
+ (list retry-v1)))
+ (more-effects
+ (contract "r2" (list idempotency)
+ (list unique-effect cancellation) (list retry-v1)
+ effects: '("external-operation" "audit-write"))))
+ (check-equal?
+ (.ref (sdlc-design-replacement-review baseline weakened) 'blockers)
+ '(weaker-or-changed-guarantee))
+ (check-equal?
+ (.ref (sdlc-design-replacement-review baseline more-effects) 'blockers)
+ '(new-effect))))
+ (test-case "lost ACK exposes the difference between a key and a logical operation"
+ (let* ((logical-effect
+ (sdlc-design-guarantee
+ "unique-effect" "g2"
+ "At most one external commit per logical job operation across retries and restarts"))
+ (revised (contract "r2" (list idempotency)
+ (list logical-effect cancellation)
+ (list retry-v2)))
+ (review (sdlc-design-replacement-review baseline revised))
+ (with-adr (contract "r2" (list idempotency)
+ (list logical-effect cancellation)
+ (list retry-v2)
+ decision: "lambda-aitia-adr-0008"))
+ (decision-review
+ (sdlc-design-replacement-review baseline with-adr)))
+ (check-equal? (.ref review 'declared-substitutable?) #f)
+ (check-equal? (.ref review 'recheck-guarantees) '("unique-effect"))
+ (check-equal? (.ref decision-review 'recheck-guarantees)
+ '("cancel-before-commit" "unique-effect"))
+ (check-equal? (.ref review 'evidence-admitted?) #f)
+ (check-equal? (.ref review 'release-authorized?) #f)))
+ (test-case "declaration-level compatibility never admits new evidence"
+ (let* ((next (contract "r2" '() (list unique-effect cancellation)
+ (list retry-v1)))
+ (review (sdlc-design-replacement-review baseline next)))
+ (check-equal? (.ref review 'declared-substitutable?) #t)
+ (check-equal? (.ref review 'recheck-guarantees)
+ '("cancel-before-commit" "unique-effect"))
+ (check-equal? (.ref (sdlc-design-replacement-review
+ baseline
+ (contract "r2" (list idempotency)
+ (list unique-effect cancellation)
+ (list retry-v1)))
+ 'changed-identities)
+ '("job-executor"))
+ (check-equal? (.ref review 'release-authorized?) #f)))
+ (test-case "changing the selected ADR reopens every guarantee"
+ (let (review
+ (sdlc-design-replacement-review
+ baseline
+ (contract "r2" (list idempotency)
+ (list unique-effect cancellation) (list retry-v1)
+ decision: "ADR/job-executor-v2")))
+ (check-equal? (.ref review 'blockers) '(decision-changed))
+ (check-equal? (.ref review 'changed-identities) '("job-executor"))
+ (check-equal? (.ref review 'recheck-guarantees)
+ '("cancel-before-commit" "unique-effect"))))
+ (test-case "local deduplication is a distinct design, not a proof-preserving override"
+ (let* ((alternative
+ (sdlc-design-contract
+ "job-executor" "r2" "ADR/local-deduplication"
+ '("execute-job" "reconcile-unknown-effect")
+ '("job-ledger" "dedup-ledger")
+ '("external-operation") '()
+ (list unique-effect cancellation) (list retry-v1)))
+ (review (sdlc-design-replacement-review baseline alternative)))
+ (check-equal? (.ref review 'declared-substitutable?) #f)
+ (check-equal? (.ref review 'blockers)
+ '(responsibility-changed state-ownership-changed
+ decision-changed))
+ (check-equal? (.ref review 'recheck-guarantees)
+ '("cancel-before-commit" "unique-effect"))))
+ (test-case "same revision mutation and orphan dependency fail closed"
+ (check-exception
+ (sdlc-design-replacement-review
+ baseline
+ (contract "r1" (list idempotency) (list cancellation)
+ (list retry-v1)))
+ Error?)
+ (check-exception
+ (contract "r2" (list idempotency) (list cancellation)
+ (list retry-v1))
+ Error?)
+ (check-exception
+ (contract "r2" (list idempotency idempotency)
+ (list unique-effect cancellation) (list retry-v1))
+ Error?)
+ (check-exception
+ (contract "r2" (list idempotency) '() '())
+ Error?)
+ (check-exception
+ (sdlc-design-variation "retry-policy" "v3" '())
+ Error?))))
diff --git a/t/sdlc/unit/design-implementation-test.ss b/t/sdlc/unit/design-implementation-test.ss
new file mode 100644
index 0000000..2225a28
--- /dev/null
+++ b/t/sdlc/unit/design-implementation-test.ss
@@ -0,0 +1,105 @@
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+(import :std/test :std/error
+ (only-in :clan/poo/object .ref)
+ :poo-flow/lambda-aitia/modules/assurance/interface
+ :poo-flow/lambda-aitia/modules/sdlc/interface)
+
+(export design-implementation-test)
+
+(def digest-a (string-append "sha256:" (make-string 64 #\a)))
+(def digest-b (string-append "sha256:" (make-string 64 #\b)))
+(def guarantee
+ (sdlc-design-guarantee "unique-effect" "g1" "At most one external commit"))
+(def cancellation
+ (sdlc-design-guarantee "cancel-before-commit" "g1" "No new commit after cancellation"))
+(def (contract revision guarantees)
+ (sdlc-design-contract "job-executor" revision "ADR/job-executor"
+ '("execute-job") '("job-ledger")
+ '("external-operation") '() guarantees '()))
+(def baseline (contract "r1" (list guarantee)))
+(def source
+ (assurance-artifact "artifact/job-executor" "s1" 'supported digest-a
+ owner: "lambda-aitia" media-kind: 'scheme-source
+ provenance: '("repository")))
+(def (snapshot source-value
+ source-revisions: (revisions (list (cons "artifact/job-executor" "s1")))
+ unresolved: (unresolved '()))
+ (assurance-snapshot "snapshot/job-executor" "cut-1" "graph/job-executor"
+ revisions '() "fact-cut-1" "policy/design" "p1"
+ (list source-value) '() unresolved: unresolved))
+(def current-snapshot (snapshot source))
+(def current-link
+ (sdlc-design-implementation-link baseline guarantee source current-snapshot))
+
+(def design-implementation-test
+ (test-suite "SDLC design to source mapping"
+ (test-case "exact assurance snapshot binding is current but not conformance"
+ (let (review
+ (sdlc-design-implementation-review baseline current-snapshot
+ (list current-link)))
+ (check-equal? (.ref review 'mapping-current?) #t)
+ (check-equal? (.ref review 'mapped-guarantees) '("unique-effect"))
+ (check-equal? (.ref review 'unmapped-guarantees) '())
+ (check-equal? (.ref review 'implementation-conforms?) #f)
+ (check-equal? (.ref review 'evidence-admitted?) #f)
+ (check-equal? (.ref review 'release-authorized?) #f)))
+ (test-case "new contract revision or unmapped guarantee reopens coverage"
+ (let* ((next (contract "r2" (list guarantee cancellation)))
+ (review (sdlc-design-implementation-review
+ next current-snapshot (list current-link))))
+ (check-equal? (.ref review 'mapping-current?) #f)
+ (check-equal? (.ref review 'unmapped-guarantees)
+ '("cancel-before-commit" "unique-effect"))
+ (check-equal? (.ref (car (.ref review 'blocked-links)) 'blocker)
+ 'contract-changed)))
+ (test-case "same-revision contract mutation cannot reuse an old link"
+ (let* ((changed-guarantee
+ (sdlc-design-guarantee "unique-effect" "g1" "At most two commits"))
+ (changed-contract (contract "r1" (list changed-guarantee)))
+ (review (sdlc-design-implementation-review
+ changed-contract current-snapshot (list current-link))))
+ (check-equal? (.ref review 'mapping-current?) #f)
+ (check-equal? (.ref (car (.ref review 'blocked-links)) 'blocker)
+ 'contract-changed)))
+ (test-case "source change invalidates exact snapshot binding"
+ (let* ((new-source
+ (assurance-artifact "artifact/job-executor" "s1" 'supported digest-b
+ owner: "lambda-aitia" media-kind: 'scheme-source
+ provenance: '("repository")))
+ (changed (snapshot new-source))
+ (review (sdlc-design-implementation-review
+ baseline changed (list current-link))))
+ (check-equal? (.ref review 'mapping-current?) #f)
+ (check-equal? (.ref review 'unmapped-guarantees) '("unique-effect"))
+ (check-equal? (.ref (car (.ref review 'blocked-links)) 'blocker)
+ 'snapshot-changed)))
+ (test-case "unbound source and unresolved snapshot cannot qualify"
+ (let* ((unbound (snapshot source source-revisions: '()))
+ (link (sdlc-design-implementation-link
+ baseline guarantee source unbound))
+ (review (sdlc-design-implementation-review baseline unbound
+ (list link)))
+ (frontier (snapshot source unresolved: '("artifact/missing")))
+ (frontier-link
+ (sdlc-design-implementation-link baseline guarantee source frontier))
+ (frontier-review
+ (sdlc-design-implementation-review baseline frontier
+ (list frontier-link))))
+ (check-equal? (.ref (car (.ref review 'blocked-links)) 'blocker)
+ 'source-unbound)
+ (check-equal? (.ref frontier-review 'mapping-current?) #f)
+ (check-equal? (.ref frontier-review 'snapshot-frontier?) #t)))
+ (test-case "a different source presentation cannot match a canonical cut"
+ (let* ((different
+ (assurance-artifact "artifact/job-executor" "s1" 'supported digest-b
+ owner: "lambda-aitia" media-kind: 'scheme-source
+ provenance: '("repository")))
+ (link (sdlc-design-implementation-link
+ baseline guarantee different current-snapshot))
+ (review (sdlc-design-implementation-review baseline current-snapshot
+ (list link))))
+ (check-equal? (.ref (car (.ref review 'blocked-links)) 'blocker)
+ 'source-changed)))))
diff --git a/t/sdlc/unit/design-verification-test.ss b/t/sdlc/unit/design-verification-test.ss
new file mode 100644
index 0000000..eb8e515
--- /dev/null
+++ b/t/sdlc/unit/design-verification-test.ss
@@ -0,0 +1,323 @@
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+(import :std/test
+ (only-in :std/list/list find)
+ (only-in :clan/poo/object .o .ref)
+ (only-in :poo-flow/src/feature-system/source-lock-feature
+ source-lock-payload-digest)
+ :poo-flow/lambda-aitia/modules/assurance/interface
+ :poo-flow/lambda-aitia/modules/sdlc/interface)
+
+(export design-verification-test)
+
+(def source-bytes "(def (execute-job) 'committed)\n")
+(def digest-a (source-lock-payload-digest source-bytes))
+(def digest-b (string-append "sha256:" (make-string 64 #\b)))
+(def guarantee
+ (sdlc-design-guarantee "unique-effect" "g1" "At most one external commit"))
+(def contract
+ (sdlc-design-contract "job-executor" "r1" "ADR/job-executor"
+ '("execute-job") '("job-ledger")
+ '("external-operation") '() (list guarantee) '()))
+(def source
+ (assurance-artifact "artifact/source" "s1" 'supported digest-a
+ owner: "lambda-aitia" media-kind: 'scheme-source
+ provenance: '("repository")))
+(def other-source
+ (assurance-artifact "artifact/other" "s1" 'supported digest-b
+ owner: "lambda-aitia" media-kind: 'scheme-source
+ provenance: '("repository")))
+(def claim
+ (assurance-claim "claim/unique-effect" "r1" 'unknown digest-a
+ subject: "job-executor"
+ predicate: "At most one external commit"
+ support-requirements: '("obligation/test")
+ scope: "repository" valid-from: "2026-09-24"))
+(def obligation
+ (assurance-obligation "obligation/test" "r1" 'unknown digest-a
+ subject: "job-executor" claim: "claim/unique-effect"
+ snapshot: "snapshot/job-executor"
+ evidence-kind: 'native-test capability: 'gerbil-test
+ scope: "repository"))
+(def evidence
+ (assurance-evidence "evidence/test" "r1" 'supported digest-b
+ producer: "gxtest" tool: "gerbil-test" tool-version: "v19"
+ input-artifacts: '("artifact/source")
+ obligation: "obligation/test" subject: "job-executor"
+ scope: "repository" valid-from: "2026-09-24"
+ admission-state: 'candidate))
+(def other-evidence
+ (assurance-evidence "evidence/test" "r1" 'supported digest-b
+ producer: "gxtest" tool: "gerbil-test" tool-version: "v19"
+ input-artifacts: '("artifact/other")
+ obligation: "obligation/test" subject: "job-executor"
+ scope: "repository" valid-from: "2026-09-24"
+ admission-state: 'candidate))
+(def evidence-link
+ (assurance-relation "relation/evidence" 'assurance 'discharges
+ "evidence/test" "obligation/test" 'observed))
+(def requirement-link
+ (assurance-relation "relation/requirement" 'assurance 'supports
+ "obligation/test" "claim/unique-effect" 'declared))
+(def source-link
+ (assurance-relation "relation/source" 'structural 'depends-on
+ "claim/unique-effect" "artifact/source" 'declared))
+
+(def (construct obligation-value evidence-value relation-values)
+ (assurance-snapshot
+ "snapshot/job-executor" "cut-1" "graph/job-executor"
+ '(("artifact/source" . "s1") ("artifact/other" . "s1"))
+ '(("claim/unique-effect" . "r1"))
+ "fact-cut-1" "policy/design" "p1"
+ (list source other-source claim obligation-value evidence-value) relation-values))
+(def (bound-cut relation-values evidence-value)
+ (let (draft (construct obligation evidence-value relation-values))
+ (construct (assurance-bind-obligation-to-snapshot obligation draft)
+ evidence-value relation-values)))
+(def (bound-obligation snapshot-value)
+ (find (lambda (node)
+ (equal? (.ref node 'identity) "obligation/test"))
+ (.ref snapshot-value 'nodes)))
+(def (outcome snapshot-value input-source)
+ (assurance-verifier-outcome
+ "outcome/test" 'succeeded
+ producer: "gxtest" tool: "gerbil-test" tool-version: "v19"
+ obligation: "obligation/test" subject: "job-executor" scope: "repository"
+ snapshot-revision: (.ref snapshot-value 'revision)
+ snapshot-context-digest: (.ref snapshot-value 'context-digest)
+ inputs: (list (assurance-verifier-input input-source))
+ output-digest: digest-b))
+(def (host source-procedure source-reader: (reader #f))
+ (assurance-verification-host
+ "host/design-verification"
+ (if reader
+ (lambda (subject-value issued-at expires-at inputs)
+ (and (= (length inputs) 1)
+ (equal? (.ref (car inputs) 'identity) "artifact/source")
+ (equal? (.ref (car inputs) 'payload) source-bytes)
+ (equal? (.ref (.ref subject-value 'outcome) 'output-digest)
+ digest-b)
+ (< issued-at expires-at)))
+ (lambda (subject-value issued-at expires-at)
+ (and (equal? (.ref (.ref subject-value 'outcome) 'output-digest)
+ digest-b)
+ (< issued-at expires-at))))
+ (lambda () 11) 9 source-procedure source-reader: reader))
+(def (review snapshot-value host-value input-source evidence-value)
+ (let* ((bound (bound-obligation snapshot-value))
+ (reported (outcome snapshot-value input-source))
+ (subject (assurance-verification-subject
+ snapshot-value bound evidence-value reported))
+ (admission (assurance-host-admit
+ host-value bound evidence-value reported))
+ (mapping
+ (sdlc-design-implementation-link contract guarantee source
+ snapshot-value)))
+ (values
+ (sdlc-design-guarantee-support-review
+ host-value contract guarantee (list mapping) claim subject
+ evidence-link admission (if admission (list admission) '()))
+ admission subject mapping)))
+
+(def design-verification-test
+ (test-suite "SDLC design guarantee Host support"
+ (test-case "source-bound Host admission closes support but not conformance"
+ (let* ((cut (bound-cut (list evidence-link requirement-link source-link)
+ evidence))
+ (configured (host (lambda () cut))))
+ (let-values (((receipt admission subject mapping)
+ (review cut configured source evidence)))
+ (check-equal? (.ref receipt 'source-bound-support-current?) #t)
+ (check-equal? (.ref receipt 'source-bytes-checked-support-current?) #f)
+ (check-equal? (.ref receipt 'verifier-independence-established?) #f)
+ (check-equal? (.ref receipt 'implementation-conforms?) #f)
+ (check-equal? (.ref receipt 'release-authorized?) #f)
+ (check-equal? (.ref receipt 'runtime-executed?) #f))))
+ (test-case "Host-owned source bytes must match the mapped digest"
+ (let* ((cut (bound-cut (list evidence-link requirement-link source-link)
+ evidence))
+ (configured
+ (host (lambda () cut)
+ source-reader: (lambda (identity)
+ (and (equal? identity "artifact/source")
+ source-bytes)))))
+ (let-values (((receipt admission subject mapping)
+ (review cut configured source evidence)))
+ (check-equal? (.ref receipt 'source-bytes-checked-support-current?) #t)
+ (check-equal? (.ref receipt 'verifier-independence-established?) #f)
+ (check-equal? (.ref receipt 'implementation-conforms?) #f))))
+ (test-case "changed Host bytes fail closed"
+ (let* ((cut (bound-cut (list evidence-link requirement-link source-link)
+ evidence))
+ (payload "different")
+ (configured
+ (host (lambda () cut)
+ source-reader: (lambda (identity) payload))))
+ (let-values (((receipt admission subject mapping)
+ (review cut configured source evidence)))
+ (check-equal? admission #f)
+ (check-equal? (.ref receipt 'source-bound-support-current?) #f)
+ (check-equal? (.ref receipt 'source-bytes-checked-support-current?) #f)
+ (set! payload source-bytes)
+ (check-equal?
+ (.ref (sdlc-design-guarantee-support-review
+ configured contract guarantee (list mapping) claim subject
+ evidence-link admission '())
+ 'source-bytes-checked-support-current?)
+ #f)
+ (let-values (((fresh-receipt fresh-admission _subject _mapping)
+ (review cut configured source evidence)))
+ (check-equal? (if fresh-admission #t #f) #t)
+ (check-equal?
+ (.ref fresh-receipt 'source-bytes-checked-support-current?)
+ #t)))))
+ (test-case "issued source-bound admission cannot revive after byte drift"
+ (let* ((cut (bound-cut (list evidence-link requirement-link source-link)
+ evidence))
+ (payload source-bytes)
+ (configured
+ (host (lambda () cut)
+ source-reader: (lambda (identity) payload))))
+ (let-values (((receipt admission subject mapping)
+ (review cut configured source evidence)))
+ (check-equal? (if admission #t #f) #t)
+ (check-equal? (.ref receipt 'source-bytes-checked-support-current?) #t)
+ (set! payload "different")
+ (check-equal?
+ (assurance-host-admission-current? configured admission) #f)
+ (set! payload source-bytes)
+ (check-equal?
+ (assurance-host-admission-current? configured admission) #f)
+ (let-values (((fresh-receipt fresh-admission _subject _mapping)
+ (review cut configured source evidence)))
+ (check-equal? (if fresh-admission #t #f) #t)
+ (check-equal?
+ (.ref fresh-receipt 'source-bytes-checked-support-current?)
+ #t)))))
+ (test-case "source bytes moving during verifier execution prevent issuance"
+ (let* ((cut (bound-cut (list evidence-link requirement-link source-link)
+ evidence))
+ (payload source-bytes)
+ (configured
+ (assurance-verification-host
+ "host/source-race"
+ (lambda (subject issued-at expires-at inputs)
+ (check-equal? (.ref (car inputs) 'payload) source-bytes)
+ (set! payload "different")
+ #t)
+ (lambda () 11) 9 (lambda () cut)
+ source-reader: (lambda (identity) payload)))
+ (reported (outcome cut source)))
+ (check-equal?
+ (assurance-host-admit
+ configured (bound-obligation cut) evidence reported)
+ #f)))
+ (test-case "verifier cannot mutate handed source bytes and keep a seal"
+ (let* ((cut (bound-cut (list evidence-link requirement-link source-link)
+ evidence))
+ (configured
+ (assurance-verification-host
+ "host/handed-input-mutation"
+ (lambda (subject issued-at expires-at inputs)
+ (let (input (car inputs))
+ (set-car! inputs
+ (.o identity: (.ref input 'identity)
+ revision: (.ref input 'revision)
+ digest: (source-lock-payload-digest "different")
+ payload: "different")))
+ #t)
+ (lambda () 11) 9 (lambda () cut)
+ source-reader: (lambda (identity) source-bytes)))
+ (reported (outcome cut source)))
+ (check-equal?
+ (assurance-host-admit
+ configured (bound-obligation cut) evidence reported)
+ #f)))
+ (test-case "a cut moving during Host source read fails closed"
+ (let* ((cut (bound-cut (list evidence-link requirement-link source-link)
+ evidence))
+ (next (bound-cut (list evidence-link requirement-link)
+ evidence))
+ (current-cut cut)
+ (configured
+ (host (lambda () current-cut)
+ source-reader:
+ (lambda (identity)
+ (set! current-cut next)
+ source-bytes))))
+ (let-values (((receipt admission subject mapping)
+ (review cut configured source evidence)))
+ (check-equal? admission #f)
+ (check-equal? (.ref receipt 'source-bytes-checked-support-current?) #f))))
+ (test-case "design assumptions cannot disappear from the Assurance Claim"
+ (let* ((cut (bound-cut (list evidence-link requirement-link source-link)
+ evidence))
+ (configured (host (lambda () cut)))
+ (assumed
+ (sdlc-design-contract
+ "job-executor" "r2" "ADR/job-executor"
+ '("execute-job") '("job-ledger") '("external-operation")
+ (list (sdlc-design-assumption
+ "provider-idempotency" "a1" "Provider deduplicates keys"))
+ (list guarantee) '()))
+ (reported (outcome cut source))
+ (bound (bound-obligation cut))
+ (subject (assurance-verification-subject
+ cut bound evidence reported))
+ (admission (assurance-host-admit configured bound evidence reported))
+ (mapping (sdlc-design-implementation-link
+ assumed guarantee source cut))
+ (receipt
+ (sdlc-design-guarantee-support-review
+ configured assumed guarantee (list mapping) claim subject
+ evidence-link admission (list admission))))
+ (check-equal? (.ref receipt 'mapping-current?) #t)
+ (check-equal? (.ref receipt 'claim-current?) #f)
+ (check-equal? (.ref receipt 'source-bound-support-current?) #f)))
+ (test-case "missing claim-to-source dependency blocks design support"
+ (let* ((cut (bound-cut (list evidence-link requirement-link) evidence))
+ (configured (host (lambda () cut))))
+ (let-values (((receipt admission subject mapping)
+ (review cut configured source evidence)))
+ (check-equal? (.ref receipt 'host-admission-current?) #t)
+ (check-equal? (.ref receipt 'source-input-bound?) #f)
+ (check-equal? (.ref receipt 'source-bound-support-current?) #f))))
+ (test-case "admission over another current source does not cover design source"
+ (let* ((cut (bound-cut (list evidence-link requirement-link source-link)
+ other-evidence))
+ (configured (host (lambda () cut))))
+ (let-values (((receipt admission subject mapping)
+ (review cut configured other-source other-evidence)))
+ (check-equal? (.ref receipt 'host-admission-current?) #t)
+ (check-equal? (.ref receipt 'source-input-bound?) #f)
+ (check-equal? (.ref receipt 'source-bound-support-current?) #f))))
+ (test-case "revoked or copied admission cannot close support"
+ (let* ((cut (bound-cut (list evidence-link requirement-link source-link)
+ evidence))
+ (configured (host (lambda () cut))))
+ (let-values (((receipt admission subject mapping)
+ (review cut configured source evidence)))
+ (let ((copied (.o (:: @ admission)))
+ (before (.ref receipt 'source-bound-support-current?)))
+ (check-equal? before #t)
+ (check-equal?
+ (.ref (sdlc-design-guarantee-support-review
+ configured contract guarantee (list mapping) claim subject
+ evidence-link admission '())
+ 'source-bound-support-current?)
+ #f)
+ (check-equal?
+ (.ref (sdlc-design-guarantee-support-review
+ configured contract guarantee (list mapping) claim subject
+ evidence-link copied (list copied))
+ 'source-bound-support-current?)
+ #f)
+ (assurance-host-revoke-admission! configured admission)
+ (check-equal?
+ (.ref (sdlc-design-guarantee-support-review
+ configured contract guarantee (list mapping) claim subject
+ evidence-link admission (list admission))
+ 'source-bound-support-current?)
+ #f)))))))
diff --git a/t/sdlc/unit/job-retirement-impact-test.ss b/t/sdlc/unit/job-retirement-impact-test.ss
new file mode 100644
index 0000000..487410d
--- /dev/null
+++ b/t/sdlc/unit/job-retirement-impact-test.ss
@@ -0,0 +1,91 @@
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+(import :std/test
+ (only-in :clan/poo/object .ref)
+ (only-in :poo-flow/src/graph/types poo-flow-graph-id)
+ (only-in :poo-flow/modules/temporal-causality/interface
+ poo-flow-structural-impact-analyze
+ poo-flow-causal-cut poo-flow-temporal-causal-classify)
+ :poo-flow/lambda-aitia/user-interface/scenarios/job-executor-retirement)
+
+(export job-retirement-impact-test)
+
+(def (impact graph (complete? #t))
+ (poo-flow-structural-impact-analyze
+ graph '(automatic-retry) job-retirement-relations 'dependents complete?))
+
+(def (classify graph)
+ (poo-flow-temporal-causal-classify
+ graph (poo-flow-causal-cut graph 4) "retry-enabled" 7))
+
+(def job-retirement-impact-test
+ (test-suite "Job-executor retirement Impact qualification"
+ (test-case "old dependents survive analysis of a proposed removal"
+ (let* ((receipt (impact job-retirement-before))
+ (affected (.ref receipt 'affected-node-ids))
+ (witness
+ (car (filter (lambda (value)
+ (eq? (.ref value 'target-node-id) 'verify-queued-retry))
+ (.ref receipt 'relation-trajectories)))))
+ (check (length affected) => 5)
+ (for-each (lambda (id) (check (if (memq id affected) #t #f) => #t))
+ '(automatic-retry historical-result active-job queued-job
+ verify-queued-retry))
+ (check (memq 'unrelated-export affected) => #f)
+ (check (.ref witness 'node-path)
+ => '(automatic-retry queued-job verify-queued-retry))
+ (check (.ref witness 'relation-path) => '(USES_CAPABILITY VERIFIES))
+ (check (.ref receipt 'release-authorized?) => #f)
+ (check (.ref receipt 'runtime-executed?) => #f)))
+ (test-case "candidate graph cannot turn missing retired identity into no impact"
+ (let (before (impact job-retirement-before))
+ (check-exception (impact job-retirement-candidate) true)
+ (check (poo-flow-graph-id job-retirement-before)
+ => 'job-executor/before-retirement)
+ (check (.ref (impact job-retirement-before) 'affected-node-ids)
+ => (.ref before 'affected-node-ids))))
+ (test-case "unknown external inventory prevents scoped completeness"
+ (let (receipt (impact job-retirement-before #f))
+ (check (.ref receipt 'status) => 'partial-impact)
+ (check (.ref receipt 'release-authorized?) => #f)))
+ (test-case "past occurrence and current activity remain separate facts"
+ (let* ((cut (poo-flow-causal-cut job-retirement-event-graph 4))
+ (receipt (classify job-retirement-event-graph)))
+ (check (.ref receipt 'past-event-ids)
+ => '("retry-enabled" "historical-result" "job-started"))
+ (check (.ref receipt 'current-event-ids)
+ => '("job-still-active" "retirement-review"))
+ (check (.ref receipt 'future-event-ids) => '("queued-retry"))
+ (check (.ref receipt 'counterfactual-event-ids) => '("retire-retry"))
+ (check (.ref receipt 'outside-horizon-event-ids) => '("later-retry"))
+ (check (.ref receipt 'hypothesized-event-ids) => '())
+ (check (member "retire-retry"
+ (map (lambda (event) (.ref event 'identity))
+ (.ref cut 'events))) => #f)
+ (check (.ref receipt 'assurance-closed?) => #f)
+ (check (.ref receipt 'runtime-executed?) => #f)))
+ (test-case "later review does not rewrite the earlier historical cut"
+ (let* ((old (poo-flow-causal-cut job-retirement-event-graph 2))
+ (current (poo-flow-causal-cut job-retirement-event-graph 4))
+ (old-events (.ref old 'events))
+ (historical (cadr old-events)))
+ (check (map (lambda (event) (.ref event 'identity)) old-events)
+ => '("retry-enabled" "historical-result" "job-started"))
+ (check (.ref historical 'payload-identity) => "historical-result/payload")
+ (check (.ref historical 'modality) => 'observed)
+ (check (.ref historical 'committed?) => #t)
+ (check (if (memq historical (.ref current 'events)) #t #f) => #t)
+ (check (map (lambda (event) (.ref event 'identity)) (.ref old 'events))
+ => '("retry-enabled" "historical-result" "job-started"))))
+ (test-case "a missing causal parent remains an unknown frontier"
+ (let (receipt (classify job-retirement-incomplete-event-graph))
+ (check (.ref receipt 'status) => 'partial-temporal-classification)
+ (check (.ref receipt 'unknown-frontier) => '("unknown-consumer-origin"))
+ (check (.ref receipt 'release-authorized?) => #f)))
+ (test-case "a proposal cannot be promoted to the observed change trigger"
+ (check-exception
+ (poo-flow-temporal-causal-classify
+ job-retirement-event-graph (poo-flow-causal-cut job-retirement-event-graph 4)
+ "retire-retry" 7)
+ true))))
diff --git a/t/sdlc/unit/nasa-catalog-test.ss b/t/sdlc/unit/nasa-catalog-test.ss
index f66763a..82231d7 100644
--- a/t/sdlc/unit/nasa-catalog-test.ss
+++ b/t/sdlc/unit/nasa-catalog-test.ss
@@ -3,7 +3,7 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import :std/test :std/error
- :poo-flow/src/module-system/contribution/interface
+ (only-in :clan/poo/object .ref .slot?)
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d
:std/list/list)
(export nasa-catalog-test)
@@ -15,7 +15,7 @@
(check-equal? (length (delete-duplicates/hash (map (lambda (r) (.ref r 'identity)) rows))) 130)
(check-equal? (length (filter (lambda (r) (.slot? r 'class-matrix)) rows)) 100)
(for-each (lambda (r)
- (check-equal? (.ref r 'standard) "nasa/npr-7150.2d")
+ (check-equal? (.ref r 'standard) "nasa/sdlc/npr-7150.2")
(check-equal? (.ref r 'assessment) 'not-evaluated)) rows)))
(test-case "matrix blanks never become unconditional exemptions"
(check-equal? (nasa-matrix-invocation "SWE-013" 'e) 'invoked)
diff --git a/t/sdlc/unit/nasa-certification-test.ss b/t/sdlc/unit/nasa-certification-test.ss
index 01d8469..35a5654 100644
--- a/t/sdlc/unit/nasa-certification-test.ss
+++ b/t/sdlc/unit/nasa-certification-test.ss
@@ -4,7 +4,7 @@
(import :std/test :std/error
(only-in :clan/poo/object .o .ref)
- :poo-flow/src/module-system/contribution/verification
+ :poo-flow/core/contribution/verification
:poo-flow/lambda-aitia/modules/sdlc/objects
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-policy
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-certification)
diff --git a/t/sdlc/unit/nasa-lifecycle-test.ss b/t/sdlc/unit/nasa-lifecycle-test.ss
index 7d95eb5..81eda22 100644
--- a/t/sdlc/unit/nasa-lifecycle-test.ss
+++ b/t/sdlc/unit/nasa-lifecycle-test.ss
@@ -4,7 +4,7 @@
(import :std/test :std/error
(only-in :clan/poo/object .o .ref)
- :poo-flow/src/module-system/contribution/verification
+ :poo-flow/core/contribution/verification
:poo-flow/lambda-aitia/modules/sdlc/objects
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-coverage
diff --git a/t/sdlc/unit/nasa-review-test.ss b/t/sdlc/unit/nasa-review-test.ss
index a28c7de..4d52b5f 100644
--- a/t/sdlc/unit/nasa-review-test.ss
+++ b/t/sdlc/unit/nasa-review-test.ss
@@ -3,7 +3,7 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import :std/test :std/error
- :poo-flow/src/module-system/contribution/interface
+ (only-in :clan/poo/object .o .ref)
:poo-flow/lambda-aitia/modules/sdlc/interface
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d-catalog
diff --git a/t/sdlc/unit/nasa-standard-provider-test.ss b/t/sdlc/unit/nasa-standard-provider-test.ss
index 1298283..b5779a8 100644
--- a/t/sdlc/unit/nasa-standard-provider-test.ss
+++ b/t/sdlc/unit/nasa-standard-provider-test.ss
@@ -12,7 +12,7 @@
(only-in :poo-flow/src/feature-system/interface
sources-lock-freeze
write-sources-lock-module)
- (only-in :poo-flow/src/modules/standards/interface
+ (only-in :poo-flow/modules/standards/interface
poo-flow-standard-digest
poo-flow-standard-materialization-receipt-valid?
poo-flow-standard-resolution-receipt-bundle
@@ -41,6 +41,11 @@
(def nasa-standard-provider-test
(test-suite
"NASA NPR 7150.2D Standards Core Provider"
+ (test-case
+ "Standards family shares the public NASA SDLC identity"
+ (check-equal? (.ref Nasa7150_2DStandardFamily 'identity)
+ "nasa/sdlc/npr-7150.2")
+ (check-equal? (.ref Nasa7150_2DStandardFamily 'semantic-kind) 'sdlc))
(test-case
"declares all immutable NASA source facts through Sources Lock Feature"
(check-equal? (.ref Nasa7150_2DSourcesLock 'feature-id) 'sources-lock)
@@ -50,7 +55,7 @@
"source.lock.ss is the deterministic product of declarations and bytes"
(let* ((generated-lock
(sources-lock-freeze
- "lambda-aitia/nasa-npr-7150.2d/sources"
+ "lambda-aitia/nasa/sdlc/npr-7150.2/D/sources"
"2022-03-08"
Nasa7150_2DSources
read-lambda-aitia-source
diff --git a/t/sdlc/unit/nasa-structured-test.ss b/t/sdlc/unit/nasa-structured-test.ss
index 4fe282f..2aa90a6 100644
--- a/t/sdlc/unit/nasa-structured-test.ss
+++ b/t/sdlc/unit/nasa-structured-test.ss
@@ -3,8 +3,10 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import :std/test :std/error
- :poo-flow/src/module-system/contribution/interface
- :poo-flow/src/module-system/contribution/verification
+ (only-in :clan/poo/object .o .ref)
+ (only-in :poo-flow/core/contribution/verification
+ poo-flow-verify poo-flow-revoke-verification!)
+ :poo-flow/core/contribution/verification
:poo-flow/lambda-aitia/modules/sdlc/interface
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-review
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-structured
diff --git a/t/sdlc/unit/sdlc-config-test.ss b/t/sdlc/unit/sdlc-config-test.ss
index 138b716..b4baf8f 100644
--- a/t/sdlc/unit/sdlc-config-test.ss
+++ b/t/sdlc/unit/sdlc-config-test.ss
@@ -3,28 +3,30 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import :std/test :std/error
- :poo-flow/src/module-system/load
- :poo-flow/src/module-system/declaration/interface
- :poo-flow/src/module-system/contribution/interface
+ (only-in :poo-flow/src/user-interface/module-selection-syntax
+ poo-flow-modules!)
+ :poo-flow/src/user-interface/module-selection
+ (only-in :clan/poo/object .ref)
+ (only-in :poo-flow/core/contribution/objects admit-contributions)
:poo-flow/lambda-aitia/modules/sdlc/config)
(def (selected . flags)
(sdlc-config (poo-flow-user-module-selection 'custom 'sdlc flags)))
(export sdlc-config-test)
(def sdlc-config-test
(test-suite "SDLC init selection projection"
- (test-case "existing custom module syntax selects the standard"
+ (test-case "custom SDLC module is standard-free"
(let* ((bundles (poo-flow-modules!
- :custom (sdlc @ "../lambda-aitia/modules/sdlc" +nasa-7150-2d)))
+ :custom (sdlc @ "../lambda-aitia/modules/sdlc")))
(selection (caar bundles))
(contribution (sdlc-config selection))
(standards (.ref (.ref contribution 'profile) 'standards)))
(check-equal? (poo-flow-user-module-selection-entrypoint selection)
"../lambda-aitia/modules/sdlc/interface.ss")
- (check-equal? (map (lambda (s) (.ref s 'identity)) standards) '("nasa/npr-7150.2d"))
+ (check-equal? standards '())
(check-equal? (.ref (admit-contributions (list contribution) '()) 'accepted?) #t)))
(test-case "no feature means no NASA selection"
(check-equal? (.ref (.ref (selected) 'profile) 'standards) '()))
(test-case "unsupported features and unrelated modules are rejected"
- (check-exception (selected '+nasa-7150-2c) Error?)
- (check-exception (selected '+nasa-7150-2d '+nasa-7150-2d) Error?)
+ (check-exception (selected '+nasa) Error?)
+ (check-exception (selected '+npr-7150.2) Error?)
(check-exception (sdlc-config (poo-flow-user-module-selection 'custom 'other '())) Error?))))
diff --git a/t/sdlc/unit/sdlc-model-test.ss b/t/sdlc/unit/sdlc-model-test.ss
index 9869221..63824dc 100644
--- a/t/sdlc/unit/sdlc-model-test.ss
+++ b/t/sdlc/unit/sdlc-model-test.ss
@@ -3,7 +3,7 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import :std/test :std/error
- :poo-flow/src/module-system/contribution/interface
+ (only-in :clan/poo/object .o .ref)
:poo-flow/lambda-aitia/modules/sdlc/interface)
(def project (sdlc-project "flight" "r2" "software" #t))
(def evidence (sdlc-evidence "ev" "flight" "r2" "software" "nasa" "SWE-052" "test" 'pass))
diff --git a/t/sdlc/unit/sdlc-test.ss b/t/sdlc/unit/sdlc-test.ss
index 204f3a5..535d963 100644
--- a/t/sdlc/unit/sdlc-test.ss
+++ b/t/sdlc/unit/sdlc-test.ss
@@ -3,7 +3,8 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import :std/test :std/error
- :poo-flow/src/module-system/contribution/interface
+ (only-in :clan/poo/object .o .ref)
+ (only-in :poo-flow/core/contribution/objects admit-contributions)
:poo-flow/lambda-aitia/modules/sdlc/interface
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d)
(def ProjectSdlcProfile (sdlc-with-standards SdlcProfile (list Nasa7150_2D)))
@@ -24,7 +25,7 @@
(test-case "clause references preserve edition and source location"
(let ((clause (nasa-requirement-by-id "SWE-013")))
(check-equal? (.ref clause 'identity) "SWE-013")
- (check-equal? (.ref clause 'standard) "nasa/npr-7150.2d")
+ (check-equal? (.ref clause 'standard) "nasa/sdlc/npr-7150.2")
(check-equal? (.ref clause 'section) "3.1.3")
(check-equal? (.ref clause 'topic) 'lifecycle-planning)))
(test-case "other standards compose without changing SDLC or NASA"
diff --git a/t/sdlc/unit/sdlc-trace-test.ss b/t/sdlc/unit/sdlc-trace-test.ss
index 788b09c..792f5a0 100644
--- a/t/sdlc/unit/sdlc-trace-test.ss
+++ b/t/sdlc/unit/sdlc-trace-test.ss
@@ -3,7 +3,7 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
(import :std/test :std/error
- :poo-flow/src/module-system/contribution/interface
+ (only-in :clan/poo/object .o .ref)
:poo-flow/lambda-aitia/modules/sdlc/interface
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-7150-2d)
(def project (sdlc-project "flight" "r2" "software" #t))
diff --git a/testing-interface.ss b/testing-interface.ss
index 5a71dd0..d5d5865 100644
--- a/testing-interface.ss
+++ b/testing-interface.ss
@@ -13,7 +13,7 @@
testing-interface-map-profile
testing-test-selector)
:std/list/list
- (only-in :poo-flow/src/module-system/observability/testing-extension
+ (only-in :poo-flow/src/testing/testing-extension
make-poo-flow-testing-observability-profile
poo-flow-testing-observability-extension))
diff --git a/tools/build-native-library.py b/tools/build-native-library.py
index 54a89c9..874c905 100644
--- a/tools/build-native-library.py
+++ b/tools/build-native-library.py
@@ -120,6 +120,27 @@ def configure_gambit_compiler(home: Path) -> str:
return str(compiler)
+def configure_orgize_header() -> None:
+ """Make Orgize's installed public header visible to Gambit's C phase."""
+
+ gerbil_path = os.environ.get("GERBIL_PATH")
+ if not gerbil_path:
+ raise RuntimeError("GERBIL_PATH must identify the installed Gerbil packages")
+ header_root = (
+ Path(gerbil_path).resolve()
+ / "pkg/github.com/tao3k/orgize/bindings/c/include"
+ )
+ if not (header_root / "orgize.h").is_file():
+ raise RuntimeError(
+ "Orgize's public C header is absent; run gxpkg deps --install "
+ f"for the pinned POO Flow dependencies: {header_root}"
+ )
+ previous = os.environ.get("C_INCLUDE_PATH")
+ os.environ["C_INCLUDE_PATH"] = os.pathsep.join(
+ part for part in (str(header_root), previous) if part
+ )
+
+
def module_closure(project: Path) -> tuple[list[tuple[str, Path]], Path]:
expression = r'''(let* ((ctx (import-module "bindings/c/aitia-native.ss"))
(deps (gxc#find-runtime-module-deps ctx)))
@@ -166,6 +187,7 @@ def generated_define(link_source: Path, name: str) -> str:
def main() -> int:
configure_darwin_toolchain()
+ configure_orgize_header()
parser = argparse.ArgumentParser()
parser.add_argument("--output", required=True, type=Path)
arguments = parser.parse_args()
diff --git a/tools/import-nasa-catalog.py b/tools/import-nasa-catalog.py
index bdc39aa..ce65310 100644
--- a/tools/import-nasa-catalog.py
+++ b/tools/import-nasa-catalog.py
@@ -55,7 +55,7 @@
(page (list-ref row 2)) (excerpt (list-ref row 3))
(parts (list-ref row 4)) (notes (list-ref row 5))
(matrix (list-ref row 6))
- (value (.o identity: id standard: "nasa/npr-7150.2d"
+ (value (.o identity: id standard: "nasa/sdlc/npr-7150.2"
section: section-value source-url: (string-append
"https://nodis3.gsfc.nasa.gov/displayDir.cfm?Internal_ID=N_PR_7150_002D_&page_name=" page)
source-excerpt: excerpt source-parts: parts source-notes: notes
diff --git a/tools/nasa-implementation-report.ss b/tools/nasa-implementation-report.ss
index ffd4e4d..10e0592 100644
--- a/tools/nasa-implementation-report.ss
+++ b/tools/nasa-implementation-report.ss
@@ -3,7 +3,7 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
;;; Render the executable ledger; no separately maintained requirement table.
-(import :poo-flow/src/module-system/contribution/interface
+(import (only-in :clan/poo/object .ref)
:poo-flow/lambda-aitia/modules/sdlc/standards/nasa-coverage)
(def (main . args)
(display "#+title: NASA implementation coverage ledger\n\n")
diff --git a/tools/update-sources-lock.ss b/tools/update-sources-lock.ss
index c5df6e8..ef9f3dd 100644
--- a/tools/update-sources-lock.ss
+++ b/tools/update-sources-lock.ss
@@ -19,7 +19,7 @@
(def lock
(sources-lock-freeze
- "lambda-aitia/nasa-npr-7150.2d/sources"
+ "lambda-aitia/nasa/sdlc/npr-7150.2/D/sources"
"2022-03-08"
Nasa7150_2DSources
read-source
diff --git a/user-interface/compositions/aitia.ss b/user-interface/compositions/aitia.ss
index 7793f8f..1a8efdd 100644
--- a/user-interface/compositions/aitia.ss
+++ b/user-interface/compositions/aitia.ss
@@ -3,52 +3,33 @@
;;;
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
-;;; Boundary: official maintained Aitia Composition and its catalog entry.
-;;; Invariant: Aitia owns domain assembly only; selection, loading, planning and
-;;; identity rules come from POO Flow.
-
-(import (only-in :poo-flow/src/module-system/profile-composition/interface
- poo-flow-composition
- poo-flow-composition-catalog
- poo-flow-official-composition-identity
- poo-flow-scenario-case
- poo-flow-scenario-clause
- poo-flow-scenario-profile-binding
- poo-flow-scenario-stage)
- :poo-flow/lambda-aitia/modules/formal-methods/interface
+;;; Official Aitia composition over POO Flow's current Profile algebra.
+;;; Domain selection stays in the maintained Project Profile; POO Flow owns
+;;; module selection, composition, Case lowering and runtime admission.
+(import (only-in :clan/poo/object .o)
+ (only-in :poo-flow/core/module-system/schema/relations
+ poo-flow-semantic-identity)
+ (only-in :poo-flow/src/authoring/semantic-module
+ poo-flow-semantic-module)
+ (only-in :poo-flow/core/profile-composition/profile-bundle
+ poo-flow-profile-export poo-flow-module-profiles
+ profiles compose)
+ (only-in :poo-flow/core/profile-composition/selection-syntax use-module)
+ (only-in :poo-flow/src/scenario/composition-syntax user-composition)
:poo-flow/lambda-aitia/user-interface/profiles/software-engineering/project)
-(export aitia-composition-identity
- aitia-composition
- aitia-composition-catalog)
-
-(def aitia-composition-identity
- (poo-flow-official-composition-identity 'aitia))
-
-(def (aitia-default-case project-profile)
- (poo-flow-scenario-case
- 'aitia
- '()
- (list project-profile)
- (list
- (poo-flow-scenario-stage
- 'software-engineering-project
- (list
- (poo-flow-scenario-clause
- 'profile
- '(default-software-engineering-project)))))
- (list
- (poo-flow-scenario-profile-binding
- 'aitia
- 'default-software-engineering-project))))
+(export AitiaModule aitia)
-(def aitia-composition
- (poo-flow-composition
- aitia-composition-identity
- default-software-engineering-project
- aitia-default-case))
+(def AitiaModule
+ (poo-flow-semantic-module
+ (poo-flow-semantic-identity 'software-engineering 'aitia)
+ profiles:
+ (poo-flow-module-profiles
+ (poo-flow-profile-export
+ 'default
+ (.o (:: @ default-software-engineering-project)
+ stages: (.o software-engineering-project: (.o)))))))
-(def aitia-composition-catalog
- (poo-flow-composition-catalog
- (list aitia-composition)
- '(gitops sdlc ADR assurance formal-methods)))
+(user-composition aitia
+ (compose profiles
+ (use-module AitiaModule default)))
diff --git a/user-interface/config.ss b/user-interface/config.ss
index 2fb966c..1ec2abb 100644
--- a/user-interface/config.ss
+++ b/user-interface/config.ss
@@ -3,21 +3,35 @@
;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
;;; -*- Gerbil -*-
-;;; Final User Composition: provider + environment Profiles + SDLC Standard.
-(import :poo-flow/src/user-interface/config-discovery-syntax)
-(export github-gitops-sdlc)
+;;; Maintained GitHub/GitOps/SDLC Case. Selection and Case lowering belong to
+;;; POO Flow; the concrete policy Profiles remain owned by Aitia.
+(import (only-in :poo-flow/core/profile-composition/profile-bundle
+ profiles compose
+ poo-flow-profile-export poo-flow-module-profiles)
+ (only-in :poo-flow/core/profile-composition/selection-syntax use-module)
+ (only-in :poo-flow/src/scenario/composition-syntax user-composition)
+ (only-in :poo-flow/core/module-system/schema/relations
+ poo-flow-semantic-identity)
+ (only-in :poo-flow/src/authoring/semantic-module
+ poo-flow-semantic-module)
+ :poo-flow/lambda-aitia/user-interface/profiles/github/actions
+ :poo-flow/lambda-aitia/user-interface/profiles/gitops/dev
+ :poo-flow/lambda-aitia/user-interface/profiles/gitops/staging
+ :poo-flow/lambda-aitia/user-interface/profiles/gitops/production
+ :poo-flow/lambda-aitia/user-interface/profiles/nasa/sdlc)
+(export AitiaDeliveryModule github-gitops-sdlc)
-(use-composition github-gitops-sdlc
- (modules
- (use-module funflow as github
- (profile actions))
- (use-module gitops as delivery
- (profile dev)
- (profile staging)
- (profile production))
- (use-module sdlc as sdlc
- (profile nasa-7150-2d)))
- (compose
- (profile github actions)
- (profiles delivery dev staging production)
- (profile sdlc nasa-7150-2d)))
+(def AitiaDeliveryModule
+ (poo-flow-semantic-module
+ (poo-flow-semantic-identity 'software-engineering 'delivery)
+ profiles:
+ (poo-flow-module-profiles
+ (poo-flow-profile-export 'actions actions)
+ (poo-flow-profile-export 'dev dev)
+ (poo-flow-profile-export 'staging staging)
+ (poo-flow-profile-export 'production production))))
+
+(user-composition github-gitops-sdlc
+ (compose profiles
+ (use-module AitiaDeliveryModule actions dev staging production)
+ (use-module NasaSdlcModule npr-7150.2)))
diff --git a/user-interface/contracts/engineering-workspace.org b/user-interface/contracts/engineering-workspace.org
new file mode 100644
index 0000000..3af78a5
--- /dev/null
+++ b/user-interface/contracts/engineering-workspace.org
@@ -0,0 +1,95 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Aitia engineering workspace — Scheme-owned Org contracts
+
+:PROPERTIES:
+:ID: aitia-engineering-workspace-contract
+:STATUS: draft
+:END:
+
+Orgize's Scheme/POO Org Elements and Org Contract modules own these declarations.
+The tagged Scheme blocks lower to admitted POO values, then to Rust AOT plans
+consumed over the Scheme-declared Rowan graph. No legacy CLI evaluator or
+Python implementation defines the semantics. CONTRACT_ID and CONTRACT_SCOPE
+remain native Org properties; :org-contract is the upstream block selector,
+not a replacement metadata channel.
+
+These contracts check reviewable structure only. They do not resolve identity,
+verify the content of evidence, complete a review or grant execution authority.
+Consumer generation/evaluation must be qualified on the Scheme/Rowan path.
+
+* Engineering workspace
+:PROPERTIES:
+:CONTRACT_ID: lambda-aitia.engineering-workspace
+:CONTRACT_SCOPE: document
+:END:
+
+#+begin_src scheme :org-contract
+(assert-org-element "workspace.organization" error
+ (bindings)
+ (org-elements headline (property title "Organization") (child-of scope))
+ (expect at-least 1))
+(assert-org-element "workspace.requirements" error
+ (bindings)
+ (org-elements headline (property title "Requirements") (descendant-of scope))
+ (expect at-least 1))
+(assert-org-element "workspace.assumptions" error
+ (bindings)
+ (org-elements headline (property title "Assumptions") (descendant-of scope))
+ (expect at-least 1))
+(assert-org-element "workspace.alternatives" error
+ (bindings)
+ (org-elements headline (property title "Alternatives") (descendant-of scope))
+ (expect at-least 1))
+(assert-org-element "workspace.review" error
+ (bindings)
+ (org-elements headline (property title "Review") (descendant-of scope))
+ (expect at-least 1))
+(assert-org-element "workspace.receipts" error
+ (bindings)
+ (org-elements headline (property title "Receipts") (descendant-of scope))
+ (expect at-least 1))
+#+end_src
+
+* Design decision review
+:PROPERTIES:
+:CONTRACT_ID: lambda-aitia.design-review
+:CONTRACT_SCOPE: subtree
+:END:
+
+The section bindings are local to the selected decision. A required next gate
+is proving that another decision's content cannot satisfy these assertions.
+
+#+begin_src scheme :org-contract
+(assert-org-element "decision.requirements" error
+ (bindings
+ (bind requirements
+ (org-elements headline (property title "Requirements") (child-of scope))))
+ (org-elements headline (child-of requirements))
+ (expect at-least 1))
+(assert-org-element "decision.assumptions" error
+ (bindings
+ (bind assumptions
+ (org-elements headline (property title "Assumptions") (child-of scope))))
+ (org-elements headline (child-of assumptions))
+ (expect at-least 1))
+(assert-org-element "decision.alternatives" error
+ (bindings
+ (bind alternatives
+ (org-elements headline (property title "Alternatives") (child-of scope))))
+ (org-elements headline (child-of alternatives))
+ (expect at-least 2))
+(assert-org-element "decision.review" error
+ (bindings
+ (bind review
+ (org-elements headline (property title "Review") (child-of scope))))
+ (org-elements headline (child-of review))
+ (expect at-least 1))
+(assert-org-element "decision.receipts" error
+ (bindings
+ (bind receipts
+ (org-elements headline (property title "Receipts") (child-of scope))))
+ (org-elements link (descendant-of receipts))
+ (expect at-least 1))
+#+end_src
diff --git a/user-interface/profiles/github/actions.ss b/user-interface/profiles/github/actions.ss
index 1a4de3a..e39d487 100644
--- a/user-interface/profiles/github/actions.ss
+++ b/user-interface/profiles/github/actions.ss
@@ -6,7 +6,7 @@
;;; Reusable GitHub workflow Profiles; no scenario or runtime execution lives here.
(import (only-in :clan/poo/object .def)
- :poo-flow/src/modules/funflow/github-ci-contract)
+ :poo-flow/modules/funflow/github-ci-contract)
(export actions)
(.def (actions @ poo-flow-funflow-github-ci-profile)
diff --git a/user-interface/profiles/nasa/sdlc.ss b/user-interface/profiles/nasa/sdlc.ss
new file mode 100644
index 0000000..1c56435
--- /dev/null
+++ b/user-interface/profiles/nasa/sdlc.ss
@@ -0,0 +1,31 @@
+;;; -*- Gerbil -*-
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;;
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; Organization NASA / domain SDLC / standard family NPR 7150.2.
+;;; POO Flow owns the typed namespace, Module and Profile selection.
+(import (only-in :clan/poo/object .def)
+ (only-in :poo-flow/core/module-system/schema/relations
+ poo-flow-semantic-identity)
+ (only-in :poo-flow/src/authoring/semantic-module
+ poo-flow-semantic-module)
+ (only-in :poo-flow/core/profile-composition/profile-bundle
+ poo-flow-profile-export poo-flow-module-profiles)
+ :poo-flow/lambda-aitia/modules/sdlc/config)
+(export NasaSdlcModule nasa-sdlc-npr7150_2)
+
+(.def (nasa-sdlc-npr7150_2 @ sdlc-nasa-7150-profile)
+ (name 'nasa/sdlc/npr-7150.2)
+ (role 'standard)
+ (standard "nasa/sdlc/npr-7150.2")
+ (owner 'lambda-aitia)
+ (extends sdlc-nasa-7150-profile)
+ (runtime-executed #f))
+
+(def NasaSdlcModule
+ (poo-flow-semantic-module
+ (poo-flow-semantic-identity 'nasa 'sdlc)
+ profiles:
+ (poo-flow-module-profiles
+ (poo-flow-profile-export 'npr-7150.2 nasa-sdlc-npr7150_2))))
diff --git a/user-interface/profiles/sdlc/nasa-7150-2d.ss b/user-interface/profiles/sdlc/nasa-7150-2d.ss
deleted file mode 100644
index 2040d2b..0000000
--- a/user-interface/profiles/sdlc/nasa-7150-2d.ss
+++ /dev/null
@@ -1,15 +0,0 @@
-;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
-;;;
-;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
-
-;;; -*- Gerbil -*-
-(import (only-in :clan/poo/object .def)
- :poo-flow/lambda-aitia/modules/sdlc/config)
-(export nasa-7150-2d)
-(.def (nasa-7150-2d @ sdlc-nasa-7150-profile)
- (name 'nasa-7150-2d)
- (role 'standard)
- (standard "nasa/npr-7150.2d")
- (owner 'lambda-aitia)
- (extends sdlc-nasa-7150-profile)
- (runtime-executed #f))
diff --git a/user-interface/profiles/software-engineering/project.ss b/user-interface/profiles/software-engineering/project.ss
index ecd4aef..86e2a9c 100644
--- a/user-interface/profiles/software-engineering/project.ss
+++ b/user-interface/profiles/software-engineering/project.ss
@@ -7,7 +7,7 @@
;;; Invariant: the Profile selects Aitia knowledge and obligations only; module
;;; realization and Runtime execution remain separate POO Flow phases.
-(import (only-in :poo-flow/src/module-system/declaration/interface
+(import (only-in :poo-flow/src/user-interface/module-selection
poo-flow-settings
poo-flow-user-module-selection)
(only-in :poo-flow/src/user-interface/profile-core
@@ -23,17 +23,19 @@
(poo-flow-user-module-selection
'software-engineering 'gitops '(+open-gitops))
(poo-flow-user-module-selection
- 'software-engineering 'sdlc '(+nasa-7150-2d))
+ 'software-engineering 'sdlc '())
+ (poo-flow-user-module-selection
+ 'nasa 'sdlc '(+npr-7150.2))
(poo-flow-user-module-selection
'software-engineering 'ADR '(+rfc-relations))
(poo-flow-user-module-selection
'software-engineering 'assurance '(+causal-evidence))
(poo-flow-user-module-selection
- 'software-engineering 'formal-methods '(+tla+ +lean +cedar))))
+ 'software-engineering 'formal-methods '(+quint +lean +cedar))))
(poo-flow-settings
project-kind: 'software-engineering
composition: 'aitia
- formal-method-obligations: '(tla+ lean cedar)
+ formal-method-obligations: '(quint lean cedar)
runtime-executed?: #f)
'(project-kind
composition
diff --git a/user-interface/scenarios/github-gitops.ss b/user-interface/scenarios/github-gitops.ss
index e8c4866..0070f80 100644
--- a/user-interface/scenarios/github-gitops.ss
+++ b/user-interface/scenarios/github-gitops.ss
@@ -8,7 +8,11 @@
(export github-change github-check github-run-gitops)
(def (github-change event repository revision source-ref target-ref pull-request)
(gitops-change 'github event repository revision source-ref target-ref pull-request))
-(def (github-check name repository revision conclusion)
- (gitops-check name repository revision conclusion))
+(def (github-check name repository revision conclusion
+ standard-edition: (standard-edition #f)
+ source-lock-digest: (source-lock-digest #f))
+ (gitops-check name repository revision conclusion
+ standard-edition: standard-edition
+ source-lock-digest: source-lock-digest))
(def (github-run-gitops composition change checks)
(gitops-evaluate composition change checks))
diff --git a/user-interface/scenarios/job-executor-retirement.ss b/user-interface/scenarios/job-executor-retirement.ss
new file mode 100644
index 0000000..9bb216e
--- /dev/null
+++ b/user-interface/scenarios/job-executor-retirement.ss
@@ -0,0 +1,81 @@
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; Synthetic qualification facts, not observations from a production service.
+;;; Algorithms and receipt authority stay with POO Flow's public owners.
+(import (only-in :poo-flow/src/graph/types
+ poo-flow-graph poo-flow-graph-node poo-flow-graph-edge)
+ (only-in :poo-flow/modules/temporal-causality/interface
+ poo-flow-temporal-observation poo-flow-causal-event
+ poo-flow-causal-event-graph))
+
+(export job-retirement-before job-retirement-candidate
+ job-retirement-relations job-retirement-events
+ job-retirement-event-graph job-retirement-incomplete-event-graph)
+
+(def job-retirement-relations '(PRODUCED_UNDER USES_CAPABILITY VERIFIES))
+
+(def job-retirement-before
+ (poo-flow-graph
+ 'job-executor/before-retirement
+ (map poo-flow-graph-node
+ '(automatic-retry historical-result active-job queued-job
+ verify-queued-retry unrelated-export))
+ (list
+ (poo-flow-graph-edge 'historical-result 'automatic-retry 'PRODUCED_UNDER)
+ (poo-flow-graph-edge 'active-job 'automatic-retry 'USES_CAPABILITY)
+ (poo-flow-graph-edge 'queued-job 'automatic-retry 'USES_CAPABILITY)
+ (poo-flow-graph-edge 'verify-queued-retry 'queued-job 'VERIFIES))))
+
+;;; Candidate topology only: moving these edges is not a migration receipt.
+;;; Historical interpretation still binds job-retirement-before, not this graph.
+(def job-retirement-candidate
+ (poo-flow-graph
+ 'job-executor/candidate-after-retirement
+ (map poo-flow-graph-node
+ '(reviewed-recovery historical-result active-job queued-job
+ verify-queued-retry unrelated-export))
+ (list
+ (poo-flow-graph-edge 'active-job 'reviewed-recovery 'USES_CAPABILITY)
+ (poo-flow-graph-edge 'queued-job 'reviewed-recovery 'USES_CAPABILITY)
+ (poo-flow-graph-edge 'verify-queued-retry 'queued-job 'VERIFIES))))
+
+(def (fixture-event id kind position parents modality committed?)
+ (poo-flow-causal-event
+ id "job-executor/retirement-fixture" kind
+ (poo-flow-temporal-observation
+ (string-append id "/time") 'logical-version position
+ "synthetic-retirement-fixture")
+ (string-append id "/payload") parents modality committed?))
+
+(def job-retirement-events
+ (list
+ (fixture-event "retry-enabled" 'capability-enabled 1 '() 'observed #t)
+ (fixture-event "historical-result" 'job-completed 2
+ '("retry-enabled") 'observed #t)
+ (fixture-event "job-started" 'job-started 2
+ '("retry-enabled") 'observed #t)
+ ;; Current activity is explicitly observed; old creation time cannot infer it.
+ (fixture-event "job-still-active" 'job-state-observed 4
+ '("job-started") 'observed #t)
+ (fixture-event "retirement-review" 'review-requested 4
+ '("retry-enabled") 'observed #t)
+ (fixture-event "queued-retry" 'planned-retry 6
+ '("retry-enabled") 'declared #f)
+ (fixture-event "retire-retry" 'proposed-retirement 5
+ '("retirement-review") 'counterfactual #f)
+ (fixture-event "later-retry" 'planned-retry 10
+ '("retry-enabled") 'declared #f)
+ (fixture-event "unrelated-export" 'export-completed 4 '() 'observed #t)))
+
+(def job-retirement-event-graph
+ (poo-flow-causal-event-graph
+ "job-executor/retirement-fixture" job-retirement-events))
+
+(def job-retirement-incomplete-event-graph
+ (poo-flow-causal-event-graph
+ "job-executor/retirement-fixture"
+ (append job-retirement-events
+ (list (fixture-event "external-observation" 'consumer-observed 4
+ '("retry-enabled" "unknown-consumer-origin")
+ 'observed #t)))))
diff --git a/user-interface/scenarios/job-retirement-contract.ss b/user-interface/scenarios/job-retirement-contract.ss
new file mode 100644
index 0000000..ded0719
--- /dev/null
+++ b/user-interface/scenarios/job-retirement-contract.ss
@@ -0,0 +1,55 @@
+;;; SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+;;; SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+;;; Maintained POO design declaration for the bounded formal experiment.
+;;; Source bindings are not Host evidence admission or implementation refinement.
+(import (only-in :poo-flow/lambda-aitia/modules/sdlc/design
+ sdlc-design-contract sdlc-design-assumption sdlc-design-guarantee))
+(export job-retirement-contract job-retirement-safety
+ job-retirement-quint-sources job-retirement-lean-sources
+ job-retirement-scheme-sources)
+
+(def job-retirement-safety
+ (sdlc-design-guarantee
+ "retirement-safety" "g1"
+ "Retirement leaves no active or queued work; a cached grant cannot start a later effect."))
+(def job-retirement-contract
+ (sdlc-design-contract
+ "job-executor/retirement" "r1" "RFC/0004/retirement-ingress"
+ '("review-retirement" "invalidate-cached-authority" "drain-known-work")
+ '("retirement-review-cut") '()
+ (list
+ (sdlc-design-assumption "atomic-epoch-fence" "a1"
+ "Closing issuance and advancing the authority epoch is one atomic step.")
+ (sdlc-design-assumption "known-inventory" "a1"
+ "Inventory at finalization covers all active and queued consumers.")
+ (sdlc-design-assumption "no-new-grant" "a1"
+ "No new grant is issued after closure in the modeled scope.")
+ (sdlc-design-assumption "bounded-client" "a1"
+ "The Quint composition models one cached grant, one request and one attempt."))
+ (list job-retirement-safety) '()))
+
+;;; Dependency closure for the retirement slice, not unrelated assurance gates.
+;;; Lists name repository source assets; they are not a second workflow DSL.
+(def job-retirement-quint-sources
+ '("proof/quint/EffectAdmission.qnt"
+ "proof/quint/WorkCore.qnt"
+ "proof/quint/WorkLifecycle.qnt"
+ "proof/quint/RetirementReview.qnt"
+ "proof/quint/RetirementIngress.qnt"
+ "proof/quint/package.json"
+ "proof/quint/package-lock.json"
+ "proof/quint/tlc-config.json"
+ "proof/quint/check_contract.py"
+ "proof/quint/check_scheme_replay.py"
+ "proof/quint/process_runner.py"))
+(def job-retirement-lean-sources
+ '("proof/lean/LambdaAitiaProof/Modules/Sdlc/Retirement.lean"
+ "proof/lean/LambdaAitiaProof/Modules/Sdlc/RetirementIngress.lean"))
+(def job-retirement-scheme-sources
+ '("modules/sdlc/retirement-host.ss"
+ "t/formal/retirement-execution-test.ss"
+ "t/formal/retirement-replay-test.ss"
+ "justfile"
+ ".github/workflows/ci.yml"
+ "build.ss"))
diff --git a/user-interface/workspaces/job-executor.org b/user-interface/workspaces/job-executor.org
new file mode 100644
index 0000000..0799f5b
--- /dev/null
+++ b/user-interface/workspaces/job-executor.org
@@ -0,0 +1,289 @@
+# SPDX-FileCopyrightText: 2026 tao3k team and Contributors
+# SPDX-License-Identifier: Apache-2.0 AND LGPL-2.1-or-later
+
+#+title: Job executor — engineering design workspace
+#+startup: overview
+#+TODO: TODO REVIEW BLOCKED | DONE CANCELLED
+#+category: aitia-design
+
+:PROPERTIES:
+:ID: aitia-job-executor-workspace
+:REVISION: r2
+:STATUS: draft
+:CONTRACT_ORG: lambda-aitia.engineering-workspace
+:END:
+
+* Design question
+:PROPERTIES:
+:ID: aitia-job-executor-question
+:END:
+
+How can one logical job finish after acknowledgement loss without creating a
+second external effect? This maintained Aitia work surface connects people,
+Agent review roles, design alternatives and bounded evidence in native Org.
+Its starting decision is [[file:../../docs/adrs/0008-stable-operation-key-after-lost-ack.org][ADR 0008]], which remains proposed.
+
+This file targets the Scheme-owned Org Elements query path. Its consumer
+qualification is pending; legacy CLI queries do not close that gate.
+Role references below are proposed
+assignments, not registered principals, running Agents or granted capabilities.
+The Aitia Org-to-Scheme projection and controlled edit/execution loop remain
+pending under [[file:../../docs/rfcs/0005-org-native-engineering-workspace.org][RFC 0005]]. Reading this file executes no source block.
+
+| Dimension | Current state | Authority / source |
+|-----------+---------------+--------------------|
+| Design choice | proposed: stable logical identity | ADR 0008; independent review pending |
+| Local qualification | bounded pass and known-fault replay | linked source-fixture receipt; not provider certification |
+| Guarantee support | full support unresolved | retention, intent, concurrency and external commit boundaries open |
+| Effect authority | not granted by this workspace | existing Cedar / Runtime admission required |
+| Production outcome | not observed here | no production execution receipt |
+
+* Organization
+:PROPERTIES:
+:ID: aitia-job-executor-organization
+:END:
+
+Roles refer to the existing POO Flow Organization Bundle concepts. An
+application must resolve actual principal/role/Agent identities, visibility
+and delegations before scheduling. Org nesting does not confer authority.
+
+| Proposed role | Responsibility | Suggested context projection | Not granted by this document |
+|---------------+----------------+------------------------------+------------------------------|
+| design-owner | owns requirements, alternatives and ADR proposal | whole design and review findings | self-certification of external guarantees |
+| case-reviewer | reviews historical relevance and counterevidence | source inventory, assumptions and alternatives | mutation of provider or release policy |
+| guarantee-reviewer | checks gaps across component contracts | requirements, assumptions, owner matrix and scoped receipts | conversion of a hypothesis into proof |
+| verification-runner | requests bounded checks through Runtime | selected obligation, bound inputs and tool contract | arbitrary Org code execution or provider credentials |
+| decision-reviewer | reviews the proposed choice and its conditions | alternatives, objections, obligations and evidence references | bypassing Cedar / Runtime effect gates |
+
+The same person or Agent may fill more than one role only when the application
+permits it; a required independent review must retain actual principal
+separation. No Agent is spawned by a heading or an =OWNER_ROLE= property.
+
+* Design decision — stable logical identity
+:PROPERTIES:
+:ID: aitia-job-executor-design-decision
+:STATUS: proposed
+:CONTRACT_ORG: lambda-aitia.design-review
+:END:
+
+This subtree is the review context for ADR 0008. Its contract checks local
+requirements, assumptions, alternatives, review work and receipt references;
+passing does not accept the decision or establish any external guarantee.
+
+** Requirements
+:PROPERTIES:
+:ID: aitia-job-executor-requirements
+:END:
+
+*** Unique logical effect
+:PROPERTIES:
+:ID: aitia-job-executor-unique-effect
+:AITIA_KIND: requirement
+:OWNER_ROLE: design-owner
+:END:
+
+One logical job operation must not commit a duplicate effect within the
+qualified provider contract. Per-key uniqueness is insufficient when a retry
+can change its key. A completed review must state the scope rather than claim
+unconditional exactly-once delivery.
+
+*** Completion after transient response loss
+:PROPERTIES:
+:ID: aitia-job-executor-completion
+:AITIA_KIND: requirement
+:OWNER_ROLE: design-owner
+:END:
+
+The job must be able to complete after transient acknowledgement loss under
+explicit service-availability and retry/reconciliation conditions. Turning
+off retries cannot silently remove this requirement.
+
+** Assumptions
+:PROPERTIES:
+:ID: aitia-job-executor-assumptions
+:END:
+
+*** Provider retains logical-operation identity
+:PROPERTIES:
+:ID: aitia-job-executor-retention
+:AITIA_KIND: assumption
+:OWNER_ROLE: design-owner
+:STATUS: unqualified
+:END:
+
+The provider's deduplication scope and retention cover permitted retries.
+There is no measured retention duration in the current fixture. A shortened
+retention interval, identity collision or late request reopens this assumption.
+
+*** Effect and deduplication survive failure coherently
+:PROPERTIES:
+:ID: aitia-job-executor-atomicity
+:AITIA_KIND: assumption
+:OWNER_ROLE: guarantee-reviewer
+:STATUS: unqualified
+:END:
+
+The actual provider supplies atomic commit or a qualified recovery protocol.
+The local SQLite row stands for both the key and the effect; it does not test
+an independent external side effect. Two Cedar permits are not a uniqueness
+mechanism. A lost ACK leaves the caller uncertain, not entitled to assume no
+commit occurred.
+
+** Alternatives
+:PROPERTIES:
+:ID: aitia-job-executor-alternatives
+:END:
+
+#+name: retry-design-alternatives
+| Option | Unique logical effect | Completion | Additional obligations | Current judgment |
+|--------+-----------------------+------------+------------------------+------------------|
+| Stop after lost ACK | avoids a retry-induced duplicate, but does not establish original outcome | not guaranteed | explicit unknown outcome and recovery owner | conflicts with unattended completion unless recovery is added |
+| Retry with stable identity | conditional on scope, retention and provider semantics | conditional on availability and retry policy | parameter binding, persistence, concurrent attempts and crash checks | proposed by ADR 0008; one bounded fixture passes |
+| Query / reconcile before further action | depends on authoritative correlation and reconciliation semantics | depends on query availability and outcome resolution | bind original identity; absence must not be mistaken for failure when history is incomplete | candidate; no local implementation or receipt |
+
+These are comparisons under the same requirements, not a numeric ranking.
+The first and third options are not claimed to have been executed. Reconciliation
+may complement stable identity; the alternatives are not inherently exclusive.
+
+*** Stop after lost ACK
+:PROPERTIES:
+:ID: aitia-job-executor-option-stop
+:END:
+
+The comparison above identifies the missing completion/recovery guarantee.
+
+*** Retry with stable identity
+:PROPERTIES:
+:ID: aitia-job-executor-option-stable-identity
+:END:
+
+ADR 0008 proposes this option under the assumptions in this decision subtree.
+
+*** Query and reconcile
+:PROPERTIES:
+:ID: aitia-job-executor-option-reconcile
+:END:
+
+The comparison above retains this unimplemented candidate and its unknowns.
+
+** Historical material
+:PROPERTIES:
+:ID: aitia-job-executor-history
+:END:
+
+[[file:../../docs/plans/engineering-case-source-inventory.org][The source inventory]] retains attribution and applicability boundaries for
+the AWS idempotency article, the etcd watch report and the GitHub incident
+analysis. The first informs identity/retention questions. The other two remain
+candidate questions about missing history and cross-component assumptions;
+they are not evidence of an Aitia defect.
+
+** Review
+:PROPERTIES:
+:ID: aitia-job-executor-review
+:END:
+
+*** TODO Review retention and retry horizon :assumption:review:
+:PROPERTIES:
+:ID: aitia-job-executor-review-retention
+:OWNER_ROLE: case-reviewer
+:SUBJECT_ID: aitia-job-executor-retention
+:END:
+
+- [ ] Name the actual provider contract and identity scope.
+- [ ] Compare retry horizon with retention, including late requests.
+- [ ] Record contrary evidence and the required action outside the guarantee.
+
+Expected Agent contribution: an attributed applicability finding attached to
+this node, with proposed changes and explicit unknowns. It cannot accept the ADR.
+
+*** TODO Review guarantee composition :guarantee:review:
+:PROPERTIES:
+:ID: aitia-job-executor-review-composition
+:OWNER_ROLE: guarantee-reviewer
+:SUBJECT_ID: aitia-job-executor-atomicity
+:END:
+
+- [ ] Bind authorized intent to dispatched parameters.
+- [ ] Qualify mismatched-parameter rejection and concurrent attempts.
+- [ ] Qualify crash boundaries with the actual effect provider.
+
+Use the [[file:../../docs/adrs/0008-stable-operation-key-after-lost-ack.org][existing owner matrix]]: Host supplies trusted context, Cedar evaluates
+authorization, Runtime enforces at use, and provider mechanisms establish
+effect uniqueness. Do not add a second policy evaluator.
+
+*** BLOCKED Connect the source-bound Org review projection :integration:
+:PROPERTIES:
+:ID: aitia-job-executor-review-projection
+:OWNER_ROLE: design-owner
+:SUBJECT_ID: aitia-job-executor-workspace
+:END:
+
+The Scheme sources are located on Orgize's =codex/scheme-rowan-org-cutover=
+branch. Qualify an installable package boundary before wiring this projection:
+the current gerbil-parser Scheme dependency points back to POO Flow, and Orgize
+does not yet have a Gerbil package entrypoint. See RFC 0005's package audit.
+Reuse parser-owned identities/ranges and POO objects. Do not implement an Org
+parser in Python or infer supported bindings from the existence of the CLI.
+
+*** TODO Review the proposed ADR :decision:review:
+:PROPERTIES:
+:ID: aitia-job-executor-review-decision
+:OWNER_ROLE: decision-reviewer
+:SUBJECT_ID: lambda-aitia-adr-0008
+:END:
+
+- [ ] Review the same requirements across alternatives and retained objections.
+- [ ] Confirm the missing qualifications and responsible owners.
+- [ ] Record a decision revision; editing this TODO does not accept the ADR.
+
+** Receipts
+:PROPERTIES:
+:ID: aitia-job-executor-receipts
+:END:
+
+The [[file:../../bindings/python/qualification/job_executor/README.org][source-fixture replay receipt]] records the 2026-09-24 run at code commit
+=f3438241780e863db6c0b518d86628b540f96cd1=: fixed mode passed, while buggy mode
+failed specifically with two commits. This is a linked historical receipt, not
+a new run produced by opening this document or a trusted Host-issued admission.
+
+| Observation | Scope | Does not establish |
+|-------------+-------+--------------------|
+| Fixed strategy passed | single job, fixed payload, sequential restart and local provider | retention, multi-tenant or independent external-effect guarantees |
+| Buggy strategy failed with two commits | deliberate known-fault regression | novel bug discovery or causal proof for a production service |
+| Org contract check | workspace sections and decision-local review structure | identity resolution, role authority, evidence freshness or execution |
+
+The separate [[file:../../docs/receipts/2026-09-24-retirement-impact.org][retirement Impact receipt]] qualifies a synthetic automatic-retry
+removal analysis through POO Flow. It is not a new run of the persistent Python
+worker or evidence that queued tasks were migrated or cancelled.
+
+* Revision journal
+:PROPERTIES:
+:ID: aitia-job-executor-journal
+:END:
+:LOGBOOK:
+- Note taken on [2026-09-24 Thu] \\
+ Created the native authoring/query work surface. No role assignment or
+ production authorization was issued. Existing receipts remain linked.
+:END:
+
+Keep later judgments as new revisions with source and reason. An Agent edit
+proposal must bind the document digest, node ID, original range and expected
+old content; a concurrent edit requires review/replanning, not a blind offset
+replacement. A moved heading retains identity but gains no delegated rights.
+
+* Workflow
+
+#+begin_export mermaid
+flowchart TD
+ O["Org design node
authored"] --> Q["Orgize projection
located and queryable"]
+ Q --> R["Role-scoped review
proposed"]
+ R --> P["Node edit
pending owner acceptance"]
+ P --> C["Revised design
obligations reopened"]
+ C --> G["Cedar / Runtime handoff
qualification pending"]
+ G --> E["Actual execution receipt
required before claiming results"]
+ E --> O
+#+end_export
+
+This document is an authored work surface, not a qualification receipt.
+Scheme admission, AOT generation and actual consumer evaluation remain required.
+The diagram describes the intended interaction, not completed execution.