@mstar-harness/dsh 3.10.1 → 3.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +66 -5
- package/harness-agents/code-reviewer.md +25 -0
- package/harness-agents/qc-specialist-2.md +25 -0
- package/harness-agents/qc-specialist-3.md +25 -0
- package/harness-agents/qc-specialist.md +25 -0
- package/harness-skills/mstar-artifacts/SKILL.md +1 -1
- package/harness-skills/mstar-artifacts/references/plan-files-and-reports.md +7 -0
- package/harness-skills/mstar-artifacts/references/plan-quality-bar.md +5 -0
- package/harness-skills/mstar-artifacts/references/plan-workflow-lifecycle-contract.md +7 -0
- package/harness-skills/mstar-artifacts/references/status-and-residuals.md +9 -5
- package/harness-skills/mstar-artifacts/templates/plan.main.md +10 -0
- package/harness-skills/mstar-harness-core/SKILL.md +3 -2
- package/harness-skills/mstar-iteration/references/phase-1-prepare.md +1 -1
- package/harness-skills/mstar-iteration/references/plan-scoped-pm.md +1 -1
- package/harness-skills/mstar-roles/SKILL.md +9 -8
- package/harness-skills/mstar-roles/references/architect.md +1 -1
- package/harness-skills/mstar-roles/references/code-reviewer.md +9 -3
- package/harness-skills/mstar-roles/references/frontend-dev.md +1 -1
- package/harness-skills/mstar-roles/references/fullstack-dev-shared.md +1 -1
- package/harness-skills/mstar-roles/references/ops-engineer.md +1 -1
- package/harness-skills/mstar-roles/references/project-manager/plan-management.md +6 -0
- package/harness-skills/mstar-roles/references/project-manager.md +4 -3
- package/harness-skills/mstar-roles/references/prompt-engineer.md +1 -1
- package/harness-skills/mstar-roles/references/qa-engineer/acceptance-gate.md +25 -4
- package/harness-skills/mstar-roles/references/qa-engineer.md +29 -7
- package/harness-skills/mstar-roles/references/qc-specialist-shared.md +1 -1
- package/harness-skills/mstar-sdd/SKILL.md +5 -3
- package/harness-skills/mstar-sdd/references/file-handoffs.md +11 -5
- package/harness-skills/mstar-sdd/references/task-reviewer-prompt.md +18 -5
- package/harness-skills/mstar-use-cli/SKILL.md +165 -0
- package/harness-skills/mstar-use-cli/references/checks-and-lints.md +70 -0
- package/harness-skills/mstar-use-cli/references/plan-and-workflow.md +161 -0
- package/harness-skills/mstar-use-cli/references/preconditions.md +85 -0
- package/harness-skills/mstar-use-cli/references/status-and-registers.md +75 -0
- package/package.json +1 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Plan and workflow transport
|
|
2
|
+
|
|
3
|
+
This file carries the two coordination command families: scoped plan coordination (`plan`) and the guarded Prepare amendment plus standalone registration (`workflow`). They share one protocol — one document under a same-host write lock, one engine call per verb, the same token and failure discipline.
|
|
4
|
+
|
|
5
|
+
What lives elsewhere: field schemas, snapshot shape and lifecycle semantics belong to `mstar-artifacts`; phase semantics to `mstar-iteration`; checkout rules to `mstar-branch-worktree`. This file records only what command help cannot express — role boundaries, tokens, refusal codes, envelopes and sequence order.
|
|
6
|
+
|
|
7
|
+
## Session and address model
|
|
8
|
+
|
|
9
|
+
- `--session <absolute-json>` names an **engine-generated envelope**. It is never a caller-declared identity: the engine re-checks it against the document inside the write lock, so an envelope issued for another role, workflow or plan refuses instead of acting.
|
|
10
|
+
- There is no force flag, no holder or role argument, no takeover, and no lease-release verb. Ownership changes only through the accept / return / complete transitions.
|
|
11
|
+
- A plan session is bound to one plan; a coordinator session serves one workflow and is the only session allowed to amend, register, record evidence or close it. Coordinator bootstrap is trusted-local and limited to one per workflow.
|
|
12
|
+
- Two address forms reach the same prepared row: the pinned Assignment path, or the workflow + plan pair, which reads the row's registered Assignment path. A second fresh claim of the same row refuses with `coordination.duplicate-holder`, naming the live holder.
|
|
13
|
+
- A resume is read-only. It reports the current context; it never reacquires a released lease and never restarts execution.
|
|
14
|
+
- The bind verb is the only verb without a token: it reads, checks and claims atomically against current ownership.
|
|
15
|
+
|
|
16
|
+
## Verb → role boundary
|
|
17
|
+
|
|
18
|
+
| Session | Verbs |
|
|
19
|
+
|---|---|
|
|
20
|
+
| plan session | `mstar plan show`, `mstar plan progress`, `mstar plan residual-add`, `mstar plan residual-close`, `mstar plan handoff` |
|
|
21
|
+
| coordinator session | `mstar plan prepare`, `mstar plan accept`, `mstar plan return`, `mstar plan integration-start`, `mstar plan integration-accept`, `mstar plan complete`, `mstar plan reconcile`, `mstar plan repair-delivery-source`, `mstar workflow show-prepare`, `mstar workflow amend-prepare`, `mstar workflow evidence` |
|
|
22
|
+
| either (bootstrap / read / claim) | `mstar plan bind` |
|
|
23
|
+
|
|
24
|
+
A plan session mutates only its own row and its own register bucket. It never prepares itself: registration of the reviewed Assignment is the coordinator's act, and it is what releases the row's dependencies.
|
|
25
|
+
|
|
26
|
+
## Tokens
|
|
27
|
+
|
|
28
|
+
| Token | Where it comes from | Meaning |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| row revision | the show verb's machine output | `coordination.revision`; `0` while the row is not yet coordinated. Never the document schema version and never a date |
|
|
31
|
+
| register version | the same read | `absent` when that project register does not exist yet, otherwise the exact digest token of its bytes |
|
|
32
|
+
| handoff id | the same read | the row's *live* handoff; only the handoff verb mints one, and the read reports it once it exists |
|
|
33
|
+
| byte version | the versioned read face, or the amendment's read verb | `sha256:<64 lowercase hex>` over the exact bytes read — a document version, not a row revision |
|
|
34
|
+
|
|
35
|
+
Both the revision and the register version are **consumed** by the call that uses them. Read again after every successful mutation; a token carried across a write refuses rather than applying a stale edit.
|
|
36
|
+
|
|
37
|
+
## JSON envelopes
|
|
38
|
+
|
|
39
|
+
Machine output is a single object on stdout, with no color and no banner. In human mode stdout stays empty and the summary goes to stderr, so stdout can be piped without filtering.
|
|
40
|
+
|
|
41
|
+
Success carries the operation, the workflow and plan it applied to, the fresh revision, the document version, the session file and id, the role, and — where they apply — the handoff id, state and outcome. The read verb additionally returns the register version, the scope and the row.
|
|
42
|
+
|
|
43
|
+
Failure carries `ok: false`, the operation, a stable `code`, a message, and whichever of workflow id, plan id, holder, path, expected and actual the refusal can name. The refusal object is the contract; the message is for humans.
|
|
44
|
+
|
|
45
|
+
## Refusals
|
|
46
|
+
|
|
47
|
+
All plan and workflow refusals are mutation-free: the authoritative bytes are unchanged, and the fix is to read again — not to escalate to a force flag, which does not exist.
|
|
48
|
+
|
|
49
|
+
| Code | When |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `coordination.session-role` | the envelope is not the role that verb requires (a plan session cannot prepare, accept or complete) |
|
|
52
|
+
| `coordination.session-mismatch` | the envelope names a different document than the one resolved |
|
|
53
|
+
| `coordination.scope-mismatch` | the request reaches outside the session's scope |
|
|
54
|
+
| `coordination.workflow-not-found` | no snapshot for that workflow id under the resolved root |
|
|
55
|
+
| `coordination.not-prepared` | the workflow has no coordinator binding yet |
|
|
56
|
+
| `coordination.duplicate-holder` | a second fresh claim of an already-held row, or of an already-bound coordinator |
|
|
57
|
+
| `coordination.handoff-mismatch` | the handoff flag names something other than the row's live handoff |
|
|
58
|
+
| `coordination.invalid-transition` | the proposed document fails validation for the requested transition |
|
|
59
|
+
| `coordination.git-unavailable` | a Git-derived fact the verb needs cannot be established |
|
|
60
|
+
| `coordination.expected-version-required` / `coordination.version-conflict` | a coordinate write without a token, or with one that no longer matches the bytes |
|
|
61
|
+
| shared lock failure | another writer holds the same-host lock |
|
|
62
|
+
|
|
63
|
+
## Completion sequence
|
|
64
|
+
|
|
65
|
+
The plan session hands off; the coordinator drives the rest. The engine selects **one of two routes** from the workflow's own type and delivery kind — never inferred from anchors that happen to be absent.
|
|
66
|
+
|
|
67
|
+
| Route | When | After `accept` | What `complete` does |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| **Iteration** | `type: iteration`, or any non-standalone workflow | `integration-start` → the operator's pinned `git merge --no-ff` in the recorded integration checkout → `integration-accept` → `complete` | Done, the handoff completed, and **both** leases released — the row's execution lease and the workflow's integration-merge lease |
|
|
70
|
+
| **Standalone development** | `type: plan` with `delivery_kind: development` owning exactly one row | `complete` straight from the accepted handoff — no integration verb, no merge record | Done and the handoff completed with no integration record; only the row's execution lease is released, and the workflow stays running until its delivery evidence and the close |
|
|
71
|
+
|
|
72
|
+
Common prefix: **handoff** (plan side, leaves the row InReview) → **accept** (ownership transfer, not integration acceptance). Each command needs a token read from the immediately preceding state, and Git is the operator's action, never a side effect of a verb.
|
|
73
|
+
|
|
74
|
+
| Step | Session | What it records | Notes |
|
|
75
|
+
|---|---|---|---|
|
|
76
|
+
| handoff | plan | the immutable pinned handoff; the row stays InReview | the plan's finish line; execution ownership has not moved yet |
|
|
77
|
+
| accept | coordinator | execution ownership transfers to the coordinator | no merge happens here; this is ownership, not integration acceptance |
|
|
78
|
+
| integration-start | coordinator | the integration attempt and its pinned base, before any Git runs | **iteration route only**; reads the clean recorded integration checkout and refuses a foreign merge lease — the attempt is pinned *before* Git so a crash mid-merge stays reconcilable |
|
|
79
|
+
| merge | operator | the merge itself | **iteration route only**; an explicit pinned merge in the recorded integration worktree |
|
|
80
|
+
| integration-accept | coordinator | verified evidence of the pinned Git result | **iteration route only**; never runs a merge and never completes the row |
|
|
81
|
+
| complete | coordinator | Done, atomically | the last step of **either** route: it releases only the row's execution lease on the standalone route, and both leases on the iteration route |
|
|
82
|
+
| return / reconcile | coordinator | a failed attempt / crash recovery | `return` restores the plan owner; `reconcile` observes Git and finishes the iteration attempt without a second merge — on the standalone route it only replays an already-completed row |
|
|
83
|
+
| repair-delivery-source | coordinator | a corrected `branch.source` only | **not a normal step**: a pre-fix-snapshot exception for a registered source that wrongly equals the target, derived from the sealed accepted handoff, never replayable |
|
|
84
|
+
|
|
85
|
+
A retried start never moves the recorded base; that is what makes the pinned attempt, not the retry, the unit of recovery.
|
|
86
|
+
|
|
87
|
+
The handoff is a **byte-level pin**, not just a pointer: the digest of every report it names is taken at submission, so a cited report that changes afterwards — even by appending a section — refuses the completion step with a stale-evidence code. Finalize the QC and QA reports before handing off. When a report genuinely must change after a handoff, `return` the handoff, re-sign it against the new bytes, and let the coordinator `accept` again; there is no way to complete against the old pin.
|
|
88
|
+
|
|
89
|
+
Walkthrough with synthetic ids — a plan session drives its own row, the coordinator drives the lifecycle, and each token is read from the state the previous step left:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
# coordinator: bootstrap once per workflow, in the main worktree
|
|
93
|
+
mstar plan bind --coordinator --workflow wf-demo --json
|
|
94
|
+
|
|
95
|
+
# coordinator: register the reviewed Assignment (revision 0 = not yet coordinated)
|
|
96
|
+
mstar plan prepare --session <coordinator-session.json> --plan plan-a \
|
|
97
|
+
--assignment <control-root>/sdd/plan-a/assignment.md --expect 0 --json
|
|
98
|
+
|
|
99
|
+
# plan session: read scope, revision and the operations allowed right now
|
|
100
|
+
mstar plan bind --workflow wf-demo --plan plan-a --json
|
|
101
|
+
mstar plan show --session <plan-session.json> --json
|
|
102
|
+
|
|
103
|
+
# plan session: mutate only this row, then hand off
|
|
104
|
+
mstar plan progress --session <plan-session.json> --file progress.json --expect <revision> --json
|
|
105
|
+
mstar plan residual-add --session <plan-session.json> --file entries.json --expect <revision> --expect-register absent --json
|
|
106
|
+
mstar plan handoff --session <plan-session.json> --file handoff.json --expect <revision> --json
|
|
107
|
+
|
|
108
|
+
# coordinator: ownership
|
|
109
|
+
mstar plan accept --session <coordinator-session.json> --plan plan-a --handoff <live-handoff-id> --expect <revision> --json
|
|
110
|
+
|
|
111
|
+
# iteration route only: pin the attempt, merge, verify the pinned result
|
|
112
|
+
mstar plan integration-start --session <coordinator-session.json> --plan plan-a --handoff <live-handoff-id> --expect <revision> --json
|
|
113
|
+
git merge --no-ff --no-edit <source-sha>
|
|
114
|
+
mstar plan integration-accept --session <coordinator-session.json> --plan plan-a --handoff <live-handoff-id> --expect <revision> --json
|
|
115
|
+
|
|
116
|
+
# both routes end here; a standalone development plan reaches this line straight from accept
|
|
117
|
+
mstar plan complete --session <coordinator-session.json> --plan plan-a --handoff <live-handoff-id> --expect <revision> --json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The failure object at any step names the code; the row is unchanged, so the retry starts from a fresh read of the same row rather than from the step that failed.
|
|
121
|
+
|
|
122
|
+
## Prepare amendment
|
|
123
|
+
|
|
124
|
+
The amendment family is the only lawful way to register an approved scope expansion on a workflow that already exists. It is not a scheduler and not a general document replacement.
|
|
125
|
+
|
|
126
|
+
- The read verb is read-only: no lock, no write. It returns both byte versions — the snapshot's and the reviewed compass Markdown's — plus an admission view. An inadmissible lifecycle state is reported as data (`allowed: false` with one reason line per blocker), not as an error, so a workflow can be inspected before deciding.
|
|
127
|
+
- The amend verb requires **both** byte versions, even on the first amendment. They are byte versions, never row revisions; the bare hex form is also accepted.
|
|
128
|
+
- The patch names the main worktree branch and the rows to append, and may record the integration checkout and the plan parallelism. Appended rows are constructed by the engine — a patch never carries runtime row fields. Every existing row and unknown field survives by value; nothing is created, switched, fetched or cleaned.
|
|
129
|
+
- Refusals are specific and mutation-free: a stale token, a workflow that is not in Prepare or whose root entry is not running, evidence that execution has already started, a duplicate or malformed appended plan, an unknown or no-op patch key, a plan set or integration branch the reviewed compass does not declare, or a supplied integration checkout that fails validation. Auth and scope refusals reuse the shared codes above.
|
|
130
|
+
- Stop condition: a stale token is recovered by reading again and reviewing the new bytes, then amending with the fresh tokens. There is no force, replace, init or fallback flag, and no replacement-document path.
|
|
131
|
+
|
|
132
|
+
Walkthrough with synthetic ids:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
# coordinator: read the admission view and both byte versions (no lock, no write)
|
|
136
|
+
mstar workflow show-prepare --session <coordinator-session.json> --json
|
|
137
|
+
# -> {..., "snapshot_version": "sha256:<hex>", "compass_version": "sha256:<hex>",
|
|
138
|
+
# "plan_ids": ["plan-a"], "allowed": true, "blockers": []}
|
|
139
|
+
|
|
140
|
+
# coordinator: apply the approved delta with exactly those tokens
|
|
141
|
+
mstar workflow amend-prepare --session <coordinator-session.json> \
|
|
142
|
+
--expect-snapshot sha256:<hex> --expect-compass sha256:<hex> --input patch.json --json
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
A read that reports `allowed: false` is an answer, not a failure: fix the blocker (or abandon the amendment) before spending the tokens. A refusal from the amend step leaves every document byte-identical, so the next attempt starts from a new read.
|
|
146
|
+
|
|
147
|
+
## Standalone plan registration and delivery evidence
|
|
148
|
+
|
|
149
|
+
- Registration is create-only: it writes the workflow snapshot and the root register entry under one lock, recording the owned plan row, the project, the declared delivery kind and the branch anchors. The delivery kind is **declared, never inferred**, and each kind requires its own evidence declaration at registration. Re-running after a crash between the two writes recovers: existing snapshot bytes are kept and only the root entry is written.
|
|
150
|
+
- Delivery evidence is recorded stage by stage: a payload is merged into the snapshot's delivery block under the snapshot lock. The PR identity is recorded once and pinned to the registered branch anchors; a conflicting anchor is refused rather than overwritten. Declaring the kind of an older kind-less snapshot is a one-time act and refuses a terminal snapshot.
|
|
151
|
+
- Both verbs are authorized like the close: a coordinated workflow's document is written only for its own bound coordinator envelope; otherwise the call refuses without changing bytes.
|
|
152
|
+
- A registered `branch.source` cannot be amended by ordinary evidence: the evidence verb merges only the delivery block, and the one-time kind declaration refuses a value that conflicts with an already-registered anchor. The single exception is the legacy repair verb, which replaces **only** `branch.source` on a pre-fix snapshot whose registered source wrongly equals its target, derived from the sealed accepted handoff — it records no Done, no delivery success and no remote merge, and it is not a general anchor editor.
|
|
153
|
+
- The close consults this evidence *before* writing the terminal state, so a gate and the close can never disagree. Close order, refusal conditions and the root unregister: `references/status-and-registers.md`.
|
|
154
|
+
|
|
155
|
+
## Exit codes
|
|
156
|
+
|
|
157
|
+
| Code | When |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `0` | the operation succeeded, including an idempotent no-op and a read-only resume |
|
|
160
|
+
| `1` | engine refusal — every code above, always with no change to authoritative bytes |
|
|
161
|
+
| `2` | usage: missing or mixed address forms, unknown flag, a token that is neither the absent literal nor a version token, a non-numeric revision, a relative path where an absolute one is required, an unreadable or unparseable payload file |
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Preconditions
|
|
2
|
+
|
|
3
|
+
Most CLI friction is a precondition, not a syntax error: the command was right, the root was wrong, the cwd answered a different question, or a token came from an earlier state. This file collects the preconditions every family shares, in the order they must be established, and what each missing one looks like when it fails.
|
|
4
|
+
|
|
5
|
+
## 1. Which harness root
|
|
6
|
+
|
|
7
|
+
Commands that touch harness state resolve a harness directory first. Resolution starts from an explicit value and falls through to discovery:
|
|
8
|
+
|
|
9
|
+
1. an explicit override — the command's own root option, or the harness-dir environment variable (a `.mstarc` declaration in the repository config file is the same tier);
|
|
10
|
+
2. otherwise the documented probe: the harness directory, then any legacy harness directory, then the legacy plan directories;
|
|
11
|
+
3. otherwise nothing resolves.
|
|
12
|
+
|
|
13
|
+
The probe walks up from the start directory but **never crosses the workspace root**, and for a linked checkout the workspace root is the worktree itself. So a feature worktree cannot discover the control harness by walking up — process documents are gitignored and simply are not there.
|
|
14
|
+
|
|
15
|
+
Two resolutions coexist in the CLI and can disagree from the same cwd:
|
|
16
|
+
|
|
17
|
+
| Resolution | Derived from | Consequence |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| process root | the main worktree of the repository, not the checkout the process sits in | a command run inside a linked worktree still addresses the control harness |
|
|
20
|
+
| local probe | the process cwd, bounded by the workspace root | a feature worktree resolves whatever harness-shaped directory *it* happens to carry, or nothing |
|
|
21
|
+
|
|
22
|
+
That is the trap worth wiring into habits: **never assume cwd discovery addresses the control harness.** Where a stray legacy harness directory exists in the worktree, the local probe resolves it and reports success against the wrong root; where none exists, the same command fails against a directory the operator never chose.
|
|
23
|
+
|
|
24
|
+
Use the path-resolution command to see what the current cwd actually resolves before running anything that reads or writes process state, and name the root explicitly whenever the process may sit outside the control checkout — the root option, the environment override, or a positional control root where the command takes one.
|
|
25
|
+
|
|
26
|
+
Failure shapes: no resolvable root exits `1` and prints a guidance line naming the bounded probe, the start directory and the bootstrap verb. A linked checkout whose main worktree cannot be read refuses explicitly rather than degrading to local artifacts. A resolution that succeeds is not proof it chose the root the run intended.
|
|
27
|
+
|
|
28
|
+
## 2. Control root vs feature worktree
|
|
29
|
+
|
|
30
|
+
| Lives in | Content |
|
|
31
|
+
|---|---|
|
|
32
|
+
| control root (main worktree) | plan files, the root register, workflow snapshots, project registers, SDD scratch and review bundles |
|
|
33
|
+
| feature worktree | the product source changes under review |
|
|
34
|
+
| tracked results | knowledge and specs — they follow Git, so they are visible from every worktree |
|
|
35
|
+
|
|
36
|
+
Consequences:
|
|
37
|
+
|
|
38
|
+
- coordinator verbs belong to main-worktree residency, or to the recorded integration worktree for the integration steps; running them from a product checkout is a precondition failure, not a convenience;
|
|
39
|
+
- a command run from the feature worktree that needs process state must name the control root explicitly;
|
|
40
|
+
- the reverse also holds: writing product files from the control checkout mixes the two domains, and a worktree check will notice.
|
|
41
|
+
|
|
42
|
+
## 3. cwd neutrality
|
|
43
|
+
|
|
44
|
+
Git-derived checks derive the main worktree, the branch and clean-state facts from the **process cwd**. Inside a linked worktree that derivation answers a different question than the one the snapshot recorded, so a check can pass or fail for reasons unrelated to the workflow.
|
|
45
|
+
|
|
46
|
+
- Run topology and residency checks from a neutral cwd — the control checkout.
|
|
47
|
+
- SDD helpers that resolve a workspace take a control-root argument or an environment override, and fail closed from a linked checkout whose control register cannot be read.
|
|
48
|
+
- Treat the cwd as an input, like a flag: if the same command gave two answers, the cwd is the first thing to check.
|
|
49
|
+
|
|
50
|
+
## 4. Identity: the session envelope
|
|
51
|
+
|
|
52
|
+
- The envelope is **engine-generated**. It is obtained from the bind verb, addressed by absolute path, and re-checked against its document inside the write lock — the caller's word about who it is counts for nothing.
|
|
53
|
+
- There is no force flag, no takeover, no holder or role input, and no lease-release verb. A resume is read-only: it reports context and never reacquires ownership.
|
|
54
|
+
- One coordinator per workflow; a second bootstrap of the same workflow refuses.
|
|
55
|
+
- The envelope is a write credential, not just a parameter. It stays with the coordinator or PM session. Handing one to a leaf executor — or restating a revision token in a leaf's assignment — breaks the scoped boundary even when the resulting command would have succeeded.
|
|
56
|
+
- A relative session path is a usage error at every entry point; absolute is required because the engine compares canonical targets.
|
|
57
|
+
|
|
58
|
+
## 5. Tokens
|
|
59
|
+
|
|
60
|
+
| Token | Read from | Lifetime |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| row revision | the plan read verb's machine output | consumed by the transition that uses it; read again for the next one |
|
|
63
|
+
| register version | the same read: `absent`, or the digest of the register's bytes | consumed by the residual write that uses it |
|
|
64
|
+
| document byte version | the versioned store read, or the amendment's read verb | consumed by the replacement that uses it |
|
|
65
|
+
|
|
66
|
+
They are not interchangeable. A row revision is not a document version, a register version is not a snapshot version, and a schema version or a timestamp is none of the three. Supplying the wrong kind fails validation or refuses; supplying a consumed one refuses as stale. The recovery is identical in both cases: read again.
|
|
67
|
+
|
|
68
|
+
## 6. Order of establishment
|
|
69
|
+
|
|
70
|
+
Establish the rungs top-down. Each fails closed, so a lower rung is never silently satisfied by a guess about a higher one.
|
|
71
|
+
|
|
72
|
+
| Rung | Establish by | If missing |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| root | path resolution, then an explicit root where discovery is ambiguous | exit `1` with the probe guidance, or worse: a successful command against the wrong root |
|
|
75
|
+
| residency | run from the control checkout, or from the recorded integration checkout for integration steps | a gate refusal describing an unexpected checkout or branch |
|
|
76
|
+
| cwd | neutral for Git-derived checks | a check whose verdict disagrees with the recorded snapshot |
|
|
77
|
+
| identity | bind the session and pass its absolute envelope | a refusal naming the required role or the missing session |
|
|
78
|
+
| tokens | read the value the consuming command expects, immediately before it | a refusal for a missing or stale token |
|
|
79
|
+
| mutation | only then run the write | — |
|
|
80
|
+
|
|
81
|
+
## 7. When a precondition cannot be met
|
|
82
|
+
|
|
83
|
+
- If the CLI itself is absent, the engine-import and prose path declared by the owning topical skill governs. Fail closed with install or upgrade guidance rather than reconstructing a coordinated write by hand.
|
|
84
|
+
- Never close the gap by editing a coordination document directly: the protected documents refuse it, and where they do not, the write silently bypasses the lock, the token check and the session authorization that the verb would have applied.
|
|
85
|
+
- Never substitute a weaker check for a blocked one. Report the missing precondition — the exact command, root, cwd or token — and let the owner resolve it; a run that could not establish its preconditions has no result to report.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Coordination documents and registers
|
|
2
|
+
|
|
3
|
+
Three documents carry harness process state, and all three are written the same way: the bytes belong to locked engine writers, a replacement needs the version token of the bytes being replaced, and a refusal changes nothing. This file records that write protocol and the lifecycle verbs around it.
|
|
4
|
+
|
|
5
|
+
Field schemas, section meanings and per-document semantics are owned by `mstar-artifacts`; project-register lifecycle rules by `mstar-project-governance`; phase and close semantics by `mstar-iteration`. What follows is the CLI face.
|
|
6
|
+
|
|
7
|
+
## The three write surfaces
|
|
8
|
+
|
|
9
|
+
| Surface | Path shape | Holds | Store kind |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| root register | `{HARNESS_DIR}/status.json` | schema version, update timestamp, and the registry of active workflows (id, type, start, directory) | `status`, key `root` |
|
|
12
|
+
| workflow snapshot | `{WORKFLOW_DIR}/<workflow-id>/snapshot.json` | one lifecycle: plan rows, coordination blocks, leases, branch anchors, delivery block | `snapshot`, key = workflow id |
|
|
13
|
+
| project register | `{PROJECT_DIR}/<project-id>/residuals.json` | residual and backlog entries keyed by plan id | `residuals`, key = project id |
|
|
14
|
+
|
|
15
|
+
The root register is a registry, not a plan store: plan rows live in the snapshot, and a plan's residual state lives in the project register. Nothing is written to two of these documents by one command.
|
|
16
|
+
|
|
17
|
+
## Protection levels
|
|
18
|
+
|
|
19
|
+
**Protected** — `status`, `snapshot`, `residuals`. A bare put or delete through the store face refuses with `coordination.direct-write-refused` before writing anything, and that refusal also covers a generic-file alias whose canonical target is one of these documents. The consequence is deliberate: an accidental delete cannot drop the root register, and the store face is never the lifecycle route for a finished workflow.
|
|
20
|
+
|
|
21
|
+
**Unprotected** — the review envelope kind and unrelated generic files. They keep the ordinary put / get / list / delete contract, delete is an idempotent no-op when absent, and there is no confirmation prompt. A review envelope is not a coordination document: its schema and validation belong to the review workflow's owning skill; the store face only persists and reads it.
|
|
22
|
+
|
|
23
|
+
Enumeration reflects what exists: listing a kind prints its stored keys, one per line ascending, with no header, and an empty kind prints nothing and exits `0`. A missing backing file lists as empty rather than erroring.
|
|
24
|
+
|
|
25
|
+
## Versioned replacement
|
|
26
|
+
|
|
27
|
+
A coordinated replacement is two commands, and the token from the first is the only thing the second accepts.
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
# 1. read the bytes and their version
|
|
31
|
+
mstar persist get snapshot --key <workflow-id> --versioned
|
|
32
|
+
|
|
33
|
+
# 2. replace exactly those bytes
|
|
34
|
+
mstar persist snapshot --key <workflow-id> --expect-version sha256:<64-hex> --file payload.json --session <coordinator-envelope>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- The versioned read prints the payload together with its version: the digest of the exact bytes read, or the literal `absent` with a null payload when the document does not exist yet. `absent` is therefore a real token, not a missing value.
|
|
38
|
+
- The versioned read requires the local store. An injected module store refuses with `coordination.local-store-required`, because no same-host compare-and-swap is promised on a pluggable store — the coordination surface will not pretend a remote module gives it one.
|
|
39
|
+
- The write replaces; it never merges. A token that no longer matches the bytes refuses with `coordination.version-conflict`: someone wrote in between, the edit is lost, and the recovery is to read again and re-apply against the new bytes.
|
|
40
|
+
- A protected kind without a token refuses with `coordination.expected-version-required` — a bare put on those kinds is a usage error, not a fast path.
|
|
41
|
+
- Replacing a coordinated snapshot also needs its coordinator envelope; the command checks the session's role, so a plan envelope refuses. The session path must be absolute — a relative one is a usage error, because the engine compares canonical targets.
|
|
42
|
+
- After a successful replacement, read again. The token was consumed by the write that used it.
|
|
43
|
+
|
|
44
|
+
## Lifecycle close
|
|
45
|
+
|
|
46
|
+
Closing one finished lifecycle is a single verb whose work has a fixed order:
|
|
47
|
+
|
|
48
|
+
1. consult the registered delivery kind and its recorded evidence;
|
|
49
|
+
2. write the terminal snapshot under the snapshot lock;
|
|
50
|
+
3. unregister the root entry, idempotently.
|
|
51
|
+
|
|
52
|
+
Everything that can go wrong is checked before step 2, so a refusal leaves both documents byte-identical:
|
|
53
|
+
|
|
54
|
+
- dangling leases or unfinished plan rows refuse;
|
|
55
|
+
- incomplete or absent delivery evidence for the registered kind refuses, and the snapshot stays running with its root entry still registered;
|
|
56
|
+
- a **coordinated** workflow refuses a session-less close — it reports that the snapshot is coordinated and that the close needs the coordinator envelope, rather than reporting the plan rows. Only that workflow's own coordinator can close it; a plan envelope is not sufficient. An uncoordinated workflow closes with or without the flag.
|
|
57
|
+
|
|
58
|
+
Failure between steps 2 and 3 is reported as a partial close. A re-run finishes it, and a fully closed retry rewrites nothing. The close verb is the lifecycle route for a finished workflow: the protected snapshot refuses the generic delete face, and the removed residual-archival command points at the register instead.
|
|
59
|
+
|
|
60
|
+
## Residual and backlog lifecycle, in place
|
|
61
|
+
|
|
62
|
+
Entries change state inside their register; no command moves or archives a residual file.
|
|
63
|
+
|
|
64
|
+
- **Plan-side residuals** are opened and closed by the plan session against its own bucket, each with a row revision and a register version token plus the closure note that carries the evidence. The register version is `absent` for a bucket that does not exist yet, otherwise the digest token of the register's bytes — supply it, or a concurrent write is lost rather than merged.
|
|
65
|
+
- **Deferred backlog** is registered and closed through the status family, under the status write lock: entry-id uniqueness and the same-day key bump happen inside the lock, and provenance fields are filled by the command rather than by the caller.
|
|
66
|
+
- **Archival is gone.** The removed command exits `1` and names the replacement: set the entry's lifecycle with its closure fields in its project register. There is no second store for closed residuals, and no command that migrates them elsewhere.
|
|
67
|
+
- A close that cannot find the entry fails loudly instead of reporting success, so an absent id is never mistaken for an already-closed one.
|
|
68
|
+
|
|
69
|
+
## Authorization
|
|
70
|
+
|
|
71
|
+
The protected writes are authorized, not merely gated:
|
|
72
|
+
|
|
73
|
+
- every coordinated document is written only through an engine-generated session envelope for its own role and scope, re-checked inside the lock;
|
|
74
|
+
- the envelope is not a credential a caller declares — it is obtained from the bind verb, and it stays with the coordinator. Handing one to a leaf executor, or restating a revision token in a leaf's assignment, is a scope violation regardless of intent;
|
|
75
|
+
- the store face's own escape hatches are narrowed: an injected store cannot serve the coordinated surface, and the protected kinds refuse the direct faces entirely.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mstar-harness/dsh",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.3",
|
|
4
4
|
"description": "Morning Star harness dsh (DeepSeek Harness) cordis function plugin — in-process engine gates (status/dispatch/lease) with hard refusal channels.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|