Skip to content

Activity variable contracts: the corpus declares what each activity reads and writes - #494

Merged
m2ux merged 3 commits into
workflowsfrom
workflow/493-activity-variable-contracts
Aug 23, 2026
Merged

Activity variable contracts: the corpus declares what each activity reads and writes#494
m2ux merged 3 commits into
workflowsfrom
workflow/493-activity-variable-contracts

Conversation

@m2ux

@m2ux m2ux commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Merge order. The guard sweep on this branch borrows main's tooling, and the field these
contracts are authored in arrives with #495. Until that merges, every activity file here reads as
schema-invalid to the server on main and seven guards fail for that one reason. #495 pins this
commit, so merging it first turns this check green with no further push; run
npm run check:all from that branch to see the same sweep pass against this corpus.

Summary

Every variable a workflow used was declared in one place: the workflow file. An activity that touched one variable and an activity that touched forty looked the same in the file that declared them, and when one workflow included an activity another had written, the inclusion was a bare path — nothing was passed, and the included activity had no way to say what it needed.

This is the corpus half of that fix. Each activity now states the session variables it reads and the ones it writes, in its own file, and the declarations move to the activities that own them. The workflow file keeps what it genuinely owns: the facts a session starts with, and the policy that spans activities. The server half — the schema field, the contribution at load, and the checker — is in the pull request against main that pins this commit.

What moves, and where to

408 declarations move from the seventeen workflow files onto the 98 activities that produce them. Counted per workflow, before and after:

Workflow Declared before Kept in the workflow file Moved to activities Dropped
work-package 140 27 104 9
remediate-vuln 79 20 48 11
workflow-design 63 2 61 0
workflow-authoring 43 3 40 0
prism 31 7 16 8
prism-evaluate 28 0 28 0
meta 27 3 24 0
plain-language 24 4 20 0
substrate-node-security-audit 21 9 11 1
requirements-refinement 20 2 18 0
prism-audit 18 6 12 0
work-packages 18 6 11 1
cicd-pipeline-security-audit 14 2 6 6
midnight-system-review 12 6 6 0
codebase-wiki 11 0 11 0
prism-update 9 4 4 1
ponytail 7 3 4 0

A declaration stays with the workflow file for one of two reasons. Some name a fact the session is opened with — a target path, a planning folder, a branch — read by an activity before anything in the graph writes it, so the value can only have come from outside. Others are policy for the whole run: whether the package is a review of an existing pull request, whether optional discovery is skipped, which analysis type the run is doing. Both kinds are what the workflow file is for.

Two exits a return visit could not take

The check the server half adds looks for a branch decided by reading a variable that no activity on its own cycle writes. Two of those turned up in work-package, and they are the defect recorded as the second finding of #491:

Requirements elicitation chose its exit by reading whether the package needs research, and research chose its exit by reading whether elicitation is needed. Both flags were set once, at classification, and cleared by nothing. So a run that came back to either activity routed into a stage that had already finished, and the route that would skip it could not be taken, because no step was able to clear the thing it tested.

Now the stage clears its own flag: the elicitation checkpoint clears needs_elicitation when the user marks elicitation complete, and research clears needs_research once it has left its artifact. Both files are shared with remediate-vuln, so both workflows are repaired by the same edit.

What remediate-vuln was missing

remediate-vuln builds itself almost entirely out of work-package's activities: fourteen of its fifteen entries are included rather than local. Checked in the scope it actually runs in, those activities read thirteen session facts the workflow had never declared — the component's path and git directory, the tracker identity, the recorded assumption decisions, the ponytail safety-floor verdict. They are declared now, with descriptions saying what holds in a workflow whose disclosure stays private.

130 declarations, and the state behind them, removed

Forty were declared and neither read nor written anywhere in the corpus. One duplicated a field of the validation envelope beside it (validation_passed, whose only real reader is validation_results.validation_passed); one counted scanner agents that another variable's field already counted.

Ninety more were written and read by nothing, and the state that fed them goes too:

  • 52 were a checkpoint option writing a second record of the decision it already carries. The option the user chose is durable in the session's checkpoint response, so the flag beside it added a copy no definition consulted: the three assumption dispositions in the two shared fragments, the ticket-completeness verdict, the stakeholder-transcript answer, the lean-audit confirmation, the four strategic-review dispositions, the three submission answers, the closure confirmation, and six single-option confirmations across five workflows. Every option keeps the route it took, and every gate keeps the flag it tests — has_deferred_assumptions, needs_individual_interview, review_passed, client_workflow_completed and needs_simplification all stay.
  • 18 were a phase-complete marker on a path with no branch. Five audit activities announcing their own phase in a log line beside it, a wiki publish, a validation N/A marker, a GitHub issue number captured next to the found flag that gates on it, a resolved unit output directory, an escalation stage, and a GitNexus probe result prism never consulted. The log and message actions stay; two action steps holding nothing else go.
  • 12 were an operation output declared as a session variable when the technique layer was already carrying it between two operations. The operations keep their outputs; only the declarations go.
  • 13 were the checker's mistake, not the corpus's — a value reaching an operation's optional input is consumed, and the check now says so. review_type is chosen at a checkpoint and read by the operation that posts the review, which derives it from the summary's rating when unset.

Two round counters gain the read they always made: a checkpoint reached once per round carries the round in its id, so scope-confirmed#{scope_round} reads the counter the step below it bumps.

Scope

Activity contracts across all seventeen workflows, the declarations redistributed to their owners, the two routing repairs, the thirteen missing declarations, the dead state removed, and a construct-inventory row so an author reaching for a place to declare a variable finds both homes.

Non-goals

No change to how techniques declare their contracts; that boundary already worked and is already checked. Nothing here introduces private scope — the session's variables stay one flat namespace at runtime, and what changes is that the namespace has declared contributors.

Verification

npm run check:all from the paired branch — 30 guards, all pass, activity-variables at zero with no ledger. npm run check:delta reports no new findings against the merge base. Delivery cost falls 8,019 characters against the first recording of this work, and the walk baselines move only by the routing repairs and the flags the checkpoints no longer set.

m2ux and others added 2 commits August 23, 2026 11:22
An activity now states the session variables it reads and the ones it
writes, and a write declaration travels with the activity into every
workflow that includes it. The workflow file keeps what it owns: the facts
a session starts with, and the policy that spans its activities.

408 declarations move from the seventeen workflow files onto the 98
activities that produce them. work-package goes from 140 declarations to
27, remediate-vuln from 79 to 20, workflow-design from 63 to 2. 40 more
were declared and neither read nor written anywhere in the corpus — among
them one that duplicated a field of the validation envelope beside it —
and are dropped.

Two exits that a return visit could not take are fixed in passing, since
the graph check now finds them: requirements-elicitation clears
needs_elicitation when elicitation completes, and research clears
needs_research once it has left its artifact. Before this, both flags were
set once at classification and cleared by nothing, so a second pass routed
back into a stage that had already finished.

remediate-vuln gains the thirteen session facts its borrowed work-package
activities read — component paths, the tracker identity, the assumption
decisions — which it had never declared, because nothing checked a
borrowed activity in the workflow running it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An author reaching for a place to declare a variable now finds two rows
rather than one: the activity's own reads and writes, and the workflow
file's session facts and run policy. The activity row states the
contribution rule and the disagreement that fails a load, so the choice
between the two homes is answerable from the inventory alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The variable check found 90 places where a decision or a step wrote a value
no gate, loop, transition, operation or template ever read. Thirteen of
them were the checker mistaking an optional operation input for a
non-consumer; the rest were real, and this removes them.

Fifty-two were a checkpoint option writing a second record of the decision
it already carries. The option the user chose is durable in the session's
checkpoint response, so the flag beside it added a copy no definition
consulted: the three assumption dispositions in the two shared fragments,
the ticket-completeness verdict, the stakeholder-transcript answer, the
lean-audit confirmation, the four strategic-review dispositions, the three
submission answers, the closure confirmation, and six single-option
confirmations across five workflows. Every option keeps the route it took
and every gate keeps the flag it tests — has_deferred_assumptions,
needs_individual_interview, review_passed, client_workflow_completed and
needs_simplification all stay.

Eighteen were a phase-complete marker on a path with no branch: five audit
activities announcing their own phase in a log line beside it, a wiki
publish, a validation N/A marker, a GitHub issue number captured next to
the found flag that gates on it, a resolved unit output directory, an
escalation stage, and a GitNexus probe result prism never consulted. The
log and message actions stay; two action steps that held nothing else go.

Twelve were an operation output declared as a session variable when the
technique layer was already carrying it between two operations — the
per-finding dispositions inside prism-evaluate, a source-coverage flag, a
roadmap's key findings, the pull-request identity workflow-authoring
creates last, and a residual assumption list beside the gate that counts
it. The operations keep their outputs; only the declarations go.

Two round counters gain the read they always made: a checkpoint reached
once per round carries the round in its id, so scope-confirmed#{scope_round}
reads the counter that the step below it bumps.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@m2ux
m2ux merged commit 902c42e into workflows Aug 23, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant