TODO: Deterministic Spine (036)¶
ARCHIVED — PIVOTED, 2026-08-29. Work stopped after Phase 1 plus the review-round hardening. T7 and everything after it will not be built. See the archive banner in
../spec.mdand the successordocs/product-specs/040-holodeck-temporal/spec.md.Condensed task list — full acceptance criteria and verification steps in
plan.md. Restructured 2026-07-25 around an explicit MVP ship line; anchor sample retargeted from loan-hardship to the Targeted Compliance Framework. Seespec.md§ "The MVP ship line".Every unfinished task carries a sample-proof criterion — a blocking acceptance criterion naming what
sample/pbas-points/must newly demonstrate. See § "The running demonstrator" below the ship line.
Phase 0 — De-risk¶
- T1 FEEL evaluator spike (bkflow-feel → pySFeel fallback); record decision + supported subset in
research.md— chose bkflow-feel 1.2.0 (MIT); full FR-010 subset covered; caveats + static-rejection list in research.md
Phase 1 — US1: single determination node (P1)¶
- T2 Workflow Pydantic models + DAG validation +
schemas/workflow.schema.json+ sync test — shape-discriminated node union (edge/policy/human), extra=forbid closes the AI-as-verdict route (SC-008);source:annotation added; 24 tests green - T3 Decision-table model + loader + FEEL subset wrapper (static rejection w/ locator) (needs T1) —
DecisionTableownshit_policy; keyedwhencells; three failure channels (DecisionTableError / FeelValidationError w/ table·rule·cell locator / pydantic ValidationError); allowlist walker over the lark tree; 104 workflow tests green. research.md caveat 6 recordsdate(literal)-only. - T2a (amendment to landed code) Workflow-level
input_data:block — declaration, JSON-Schema validation,--inputpayload binding, no-agent-representable check (FR-025…FR-028, SC-010); re-publishworkflow.schema.json— per-fact JSON Schema validated before any node executes; SC-010 asserted against the publishedworkflow.schema.json, not just the Pydantic model; fact names resolve ininputs:but are excluded from the executed topological order - T3a (amendment to landed code)
provenance:block onDecisionTable— non-executable, not FEEL-referenceable (FR-029, FR-032) — unreachable from FEEL by two independent routes: a reserved-root check rejects it at load, and it is never placed in the evaluation context. FR-032 survived 16 evasion attempts in audit. - T4 Hit-policy evaluation UNIQUE/FIRST/PRIORITY + SC-004 conformance suite (needs T3) — standard DMN semantics for all three; loud no-match (absent a declared default) and UNIQUE multi-match;
Verdictcarries matched-rule identity + table version; SC-004 suite green - T5 Edge-node executor + schema gate (Claude-only, mocked backend tests) (needs T2) — only the validated object crosses; the gate schema is snapshotted by content; three distinct failure channels (output rejected / gate unusable / nothing produced to judge); gate validation never touches the network
- T6 Runner +
holodeck workflow run(single level, minimal OTel spans) + review-gate refusal (FR-030, SC-009) (needs T4, T5, T2a, T3a) —prepare_workflowdoes all validation and constructs no backend, making FR-003 structural rather than ordered;execute_workflowruns the DAG; the review gate refuses unreviewed generated tables at prepare time (FR-030/SC-009);holodeck workflow run [--input]maps errors to exit codes separating misauthored / could-not-decide / gate-rejected / invocation-failed - Delivered T7's implementation as a side effect:
runner._named_inputsmakes multi-level composition work at arbitrary depth. T7 is now a proof-and-cover task — see below. - ⚠ Known gap — FR-018 is NOT met.
Workflowhas noobservability:block and isextra="forbid", so the CLI has nowhere to obtain anObservabilityConfigandholodeck workflow runemits zero spans today.execute_workflowaccepts one and is unit-tested with an in-memory exporter, but that path is unreachable end to end. Closed by T13. - CHECKPOINT 1 — PASSED 2026-07-25.
make ciclean ✔ (5298 unit tests passing, 4 skipped; format, lint, type-check, security, pre-commit all clean) · single edge→policy workflow runs via CLI ✔ — exercised against a live Claude call, not a mock:sample/pbas-points/classified free text, the gate validated it, andtables/points.dmn.yamlrule 10 awarded 20 pointsper_week· human review ✔ (maintainer, who ran the live workflow). Phase 2 is unblocked.
Phase 2 — US2: composition (P1)¶
- T7 Multi-level composition — prove and cover (needs T6)
- ⚠ The wiring already landed in T6 — do not re-implement it.
runner._named_inputsresolves each declared input to a fact, an upstream gated object, or an upstreamVerdict.outputs; arbitrary depth then follows fromgraphlib. Verified on the committed code, unmodified: a 3→2→1 DAG executes('a','b','c','mid1','mid2','top')and composes correctly, withbfeeding both mid nodes (a diamond) and the top table dot-pathing intomid1.band. FR-009/FR-016 are satisfied. - What remains is proof, not build. Nothing defends composition today — there is no
test_composition.pyandtest_runner.pycovers only single-level DAGs. The audit'sall()→any()mutation in the hit-policy matcher survived because correct code with no test defending it looks exactly like broken code. Write the 3→2→1 integration test (diamond shape, dot-path into an upstream verdict), and close US2 scenario 3 through the CLI — unresolved reference → exit 2, no agent invoked. (The cycle, scenario 2, is already covered end to end.) - Sample-proof: PBAS gains a genuine two-level DRD. Footnote (1) of the source — "These tasks and activities are available to Workforce Australia Services participants only" — is an eligibility precondition attached to rows, not a points rule; it is currently encoded inline as rules 5/6 of
tables/points.dmn.yaml. Split it intotables/eligibility.dmn.yaml, keyed onactivity.activity_type+participant.stream, whose verdict is a named input topoints_award. Refactor plus a level: the verdict for every existing case, including theworkforce_australia_online+drivers_licence_attainmentcase, must not change. - Note: footnote (1) covers exactly one of the eleven modelled activities (driver's licence attainment), so the eligibility table is thin unless the taxonomy also picks up a footnote (2) row ("available to Workforce Australia Online participants only" — online learning modules, Youth Advisory Sessions). Adding one is optional but makes the level restrict in both directions instead of one.
- Deferred from the 036 review round (2026-07-25), not part of T7's proof:
runner._check_table_inputschecks only the dot-path roots an input expression reads, so a typo'd field (evidence.net_incom, where the node does declareevidence) still reaches evaluation and costs an edge-agent call. It is now cheaply checkable —PreparedWorkflow.gate_schemasholds each edge node's gate by content, andinput_dataschemas are on disk — but it is new capability, not a defect fix: a root check and a field check are different validations. Note it only holds for gates that actually declareproperties; an open gate ({}) can promise nothing about its fields. - CHECKPOINT 2 — composition proven
Phase 3 — US3: human determination (P1)¶
- T8 Human node: table recommendation + CLI prompt +
decided_by+ override flag + abort path (needs T7) - ⚠ Keep
clickout of the runner — one narrow decision seam (selected output,decided_by, timestamp, abort). CLI is the only implementation built; serve and a task-inbox UI are Out of Scope. See the transport note under Task 8 inplan.md. - ⚠ If serve is ever wired, the runner emits the AG-UI
prompt_userevents — never a model. In the normal AG-UI flow the model emits the tool call and the result lands in the model's context, which would put Claude between the delegate and the verdict. The obvious implementation is the wrong one. - ⚠ Durability (a delegate deciding tomorrow) is additive over T10+T11 — the run record is the resume token. Build none of it here; just don't design a seam that only a terminal can satisfy.
- Sample-proof: PBAS gains a
requires_humannode modelling the source's activity-bonus Note — "Providers and the Digital Services Contact Centre (DSCC) may increase the values of certain tasks or activities through an activity bonus to reflect the individual circumstances of the participant."corpus-manifest.mdnames this the corpus's canonical unmappable clause: pure discretion, no threshold to extract. The node presentspoints_awardas the recommendation and a nameddecided_bydelegate selects among the table's declared outputs. This is the sample's most important framing point — the human node exists because the source says a rule cannot. The README's "deliberately NOT modelled" item 2 moves from excluded to routed to a human. - Two source constraints, not design choices: the Note authorises an increase only (never a reduction), and it states no amount and no criteria — so no table may compute a bonus value, and the delegate's choice is a categorical selection among declared outputs (e.g. confirm / bonus applied), not free numeric entry. FR-014 already restricts the prompt to the table's declared outputs.
- CHECKPOINT 3 — scripted end-to-end with human decision; human review
Phase 4 — US4: record & replay (P2)¶
- T10 RunRecord model +
.holodeck/runs/<id>.jsonwriter withinput_data, table/gate snapshots, provenance + sha256 (needs T8) - Sample-proof: a PBAS run writes a record carrying the validated
input_datafacts (participant,claim), the gated classification, the matched rule of each policy node, the delegate's decision, and content snapshots of both tables and the gate schema with theirprovenanceblocks. - T11
holodeck workflow replay <record>— snapshot-only, zero LLM, loud integrity failures (needs T10) - Sample-proof: the PBAS record replays to an identical verdict with
sample/pbas-points/tables/andsample/pbas-points/schemas/deleted and zero LLM calls. - CHECKPOINT 4 — run → record → replay loop proven
Phase 5 — US6: TCF sample (P1 — MVP proof)¶
- T13a (spike, before T14) Is "active months" expressible? Resolve Open Question 5; if not, define how it is pre-computed into
input_data - Sample-proof: none — deliberately. This is a written verdict in
research.md, not code, and it changes nothing insample/pbas-points/(PBAS has no date arithmetic). Do not invent a sample change for it. - ⚠ Prerequisite the spike must record:
research.mdcaveat 6'sformat: date→datetime.dateconversion does not exist.input_datastores the raw JSON value, so adate-typed table column receives a string and bare subtraction raises. Both T13a and T14 depend on date arithmetic. - T14
sample/tcf-compliance/—input_dataprior state, 1 edge agent (reasonable-excuse assessment vs s5/s6), 2 tables (zoneUNIQUE, penaltyFIRST), human delegate, nodraft:block; README framing statement; scripted run + replay; integration test (needs T11, T13a) - Sample-proof: unchanged in intent — TCF is the anchor, not a PBAS extension. Additionally:
sample/pbas-points/must still run and replay after T14 lands, so the demonstrator does not rot while attention is on TCF.
▲ MVP SHIP LINE ▲¶
MVP = T4, T5, T6, T7, T8, T10, T11, T14 (+ amendments T2a, T3a). Delivers SC-001…SC-005, SC-007…SC-010. Only SC-006 falls past the line.
The running demonstrator (convention)¶
Two samples, two jobs. Neither substitutes for the other.
sample/pbas-points/is the running demonstrator. It exists from T6 onward and grows one feature per task: every task above carries a blocking sample-proof criterion naming what PBAS must newly demonstrate. A task is not done while the demonstrator cannot show its feature. It stays cheap to run — one edge agent, one live Claude call, one--inputpayload.sample/tcf-compliance/(T14) is the anchor MVP proof. A different job: a real statute, a prior-state → next-state transition, a human delegate, and a gate whose fields are drawn from an enumerated legal list (Reasonable Excuse Determination 2018 s5(2)(a)–(j)). It lands once, complete. It is not retargeted onto PBAS and its scope is not weakened by anything PBAS proves.
Why the split: PBAS proves each primitive the moment it is built, so a regression surfaces at the next task rather than at T14. TCF proves the whole claim on policy nobody can accuse us of having invented.
Phase 6 — Post-MVP¶
- Layering:
models→libinversion across two private names.models/workflow.pyimports_LITERAL_NAMES(andreferenced_roots) fromlib.workflow.feeland_RESERVED_FEEL_ROOTSfrommodels.decision_table. Two of the three are underscore-prefixed, so a model layer now depends on private members of the lib layer. It works and is not circular, but the coupling is real: node-id validity is defined by the embedded FEEL grammar, which lives inlib. Either promote the two names to public API (LITERAL_NAMES,RESERVED_FEEL_ROOTS) or move the name-validity predicate intolib.workflow.feeland have the model call one public function. Raised in the 036 review round (2026-07-25). - T9 Draft agent advisory path (
ai_may_draft: reasons) (needs T8) — restores thedraft:block to the sample - Sample-proof: PBAS's human node gains a
draft:block producing advisory reasons for the activity-bonus decision — never the points value, and never a bonus amount. The source states no amount, so a drafted number would be invented policy. - T12
WorkflowTestExecutor+holodeck workflow testwith expected-vs-actual diff (needs T4) — delivers SC-006 - Sample-proof: a committed policy-test file over
tables/points.dmn.yamlinsample/pbas-points/, covering at minimum the tier boundary at exactly 15 contact hours ("up to 15"is inclusive → 15 points, not 20) and anactivity_typethe table does not cover → loud no-match. The second case is only reachable through the policy tester: the gate's closedenumand the table's rules cover the same eleven activities, so a run can never produce it. - T13 Full OTel GenAI span attributes per specs 018/022; replay spans marked; provenance on spans (needs T11)
- Sample-proof: a PBAS run emits spans carrying node id and kind, table version, matched rule, and table provenance. This also closes T6's known gap: the CLI must be able to obtain an
ObservabilityConfigat all (todayWorkflowisextra="forbid"with noobservability:block, soholodeck workflow runemits zero spans). - T15 Docs: concepts, authoring reference, CLI usage, replay guarantees (needs T14)
- Sample-proof: the quickstart reproduces the PBAS run and replay verbatim — commands copy-paste and the printed verdict matches.
- CHECKPOINT 5 — SC-001…SC-010 mapped to tests;
make ciclean; PR
Successor¶
- 039 policy generator — AI-drafted tables + DRD from policy documents.
Spec written in parallel (
docs/product-specs/039-policy-generator/) so its schema needs land here as additions while this schema is soft. Built only after the MVP. Corpus pinned: seedocs/product-specs/039-policy-generator/corpus-manifest.md.
Open questions (human input)¶
- ~~
sample/is not tracked by git~~ — DECIDED 2026-07-25: leave ignored, defer..gitignore:45ignores/samplerepo-wide andgit ls-files sample/returns zero files; samples stay local-only. Revisit only with a concrete reason. Three consequences follow and are binding until then: - Every sample-proof criterion above is a manual check, not a CI gate. A task claiming its sample proof is asserting something no pipeline can confirm. Say so when reporting a task complete rather than implying CI covered it.
- T14's
tests/integration/test_tcf_sample.pycannot exist as specified — it would reference untracked paths and fail on a fresh clone. T14 needs rescoping before it starts: either the assertions move to committed fixtures undertests/fixtures/workflow/, or the sample proof is a documented quickstart run by hand. Do not discover this mid-task. - SC-007 keeps only its second limb. It reads "demonstrated in CI or a documented quickstart" — the quickstart route survives intact, the CI route does not. SC-007 is still satisfiable; it is not automatically verifiable.
- Confirm policy-test verb:
holodeck workflow test - Which TCF slice the sample models (Open Question 6)
- ~~Sample edge-agent model choice (Claude — which tier)~~ — settled:
anthropic/claude-opus-5,temperature: 0.0(sample/pbas-points/agents/activity-classifier.yaml), run end to end successfully (2026-07-25) - ~~Repo weight: 4.6 MB Guidelines PDF in
039/corpus/~~ — accepted, tracked in git (2026-07-25) - ~~GPLv3 acceptable for pySFeel fallback?~~ — moot, T1 chose bkflow-feel (MIT)