@gitroll/tacitt-cli 0.6.13 → 0.6.15
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/{agent-setup-Doy_AnPY.mjs → agent-setup-Bv73EZ2b.mjs} +2 -2
- package/dist/agent-setup-worker.mjs +5 -5
- package/dist/cli.mjs +10 -10
- package/dist/codex-title-worker.mjs +1 -1
- package/dist/configured-preview-worker.mjs +5 -5
- package/dist/configured-session-worker.mjs +5 -5
- package/dist/{frame-BPbypzmt.mjs → frame-0v_mLdvG.mjs} +1 -1
- package/dist/{heap-DCS_WXIA.mjs → heap-aWke8lrK.mjs} +8 -7
- package/dist/inspection-worker.mjs +4 -4
- package/dist/{job--zxwgaPy.mjs → job-ILnqW1ge.mjs} +4 -4
- package/dist/{locate-PLEyLYW-.mjs → locate-LpCex6Xj.mjs} +81 -16
- package/dist/{model-CyXvsWLz.mjs → model-BEWsfkIU.mjs} +1 -1
- package/dist/operation-executor.mjs +3 -3
- package/dist/preview-worker.mjs +1 -1
- package/dist/privacy-validator.mjs +1 -1
- package/dist/{query-BC7MIaWc.mjs → query-BX8QwaBi.mjs} +5923 -5721
- package/dist/{run-Dod7Vykp.mjs → run-P_uZgSYa.mjs} +4 -4
- package/dist/{src-CttNuxOn.mjs → src-CHy3-siB.mjs} +81 -24
- package/dist/{src-dL6y2mcu.mjs → src-ikYI-1c5.mjs} +4 -4
- package/dist/{src-CP-K2PBv.mjs → src-sERffIem.mjs} +3 -3
- package/dist/store-database-worker.mjs +1 -1
- package/dist/web/assets/index-XdWDOC5H.js +172 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/skills/tacitt/INDEX.md +1 -1
- package/skills/tacitt/SKILL.md +3 -1
- package/skills/tacitt/references/INDEX.md +2 -2
- package/skills/tacitt/references/bootstrap.md +1 -1
- package/skills/tacitt/references/classification.md +2 -0
- package/skills/tacitt/references/commands.md +17 -3
- package/skills/tacitt/references/journey.md +4 -2
- package/skills/tacitt/references/parallel-review.md +3 -3
- package/skills/tacitt/scripts/INDEX.md +5 -5
- package/skills/tacitt/scripts/journey-classification-progress.mjs +6 -0
- package/skills/tacitt/scripts/journey-classification-tasks.mjs +11 -1
- package/skills/tacitt/scripts/journey-classification-validation.d.mts +14 -1
- package/skills/tacitt/scripts/journey-classification-validation.mjs +45 -0
- package/skills/tacitt/scripts/journey-consolidation-submit.mjs +22 -5
- package/skills/tacitt/scripts/journey-scan-timing.mjs +1 -1
- package/skills/tacitt/scripts/journey-state.d.mts +3 -0
- package/skills/tacitt/scripts/journey-state.mjs +8 -1
- package/skills/tacitt/scripts/journey.mjs +45 -7
- package/skills/tacitt/scripts/privacy-validator.mjs +76 -7
- package/skills/tacitt/scripts/review.mjs +2 -2
- package/dist/web/assets/index-BgDdPL1c.js +0 -172
package/dist/web/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<meta name="referrer" content="no-referrer" />
|
|
8
8
|
<title>tacitt · Local session collection</title>
|
|
9
|
-
<script type="module" crossorigin src="./assets/index-
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-XdWDOC5H.js"></script>
|
|
10
10
|
<link rel="stylesheet" crossorigin href="./assets/index-DzOozqms.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
package/package.json
CHANGED
package/skills/tacitt/INDEX.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<!-- index-entries
|
|
7
7
|
./ | Shipped skill | Workspace-scoped instructions for direct CLI and optional Web collaboration.
|
|
8
8
|
INDEX.md | Documentation | Directory navigation.
|
|
9
|
-
SKILL.md | Agent instructions | Supplies the managed release template, explicit fast/low worker policy and phase routing into classification, paired CLI scan reruns, consent gates, account-seeded access, person-only Public choices, privacy review and approved export.
|
|
9
|
+
SKILL.md | Agent instructions | Supplies the managed release template, explicit fast/low worker policy and phase routing into classification, paired CLI scan reruns, consent gates, Retry-only formation account stop, account-seeded access, person-only Public choices, privacy review and approved export.
|
|
10
10
|
-->
|
|
11
11
|
|
|
12
12
|
The entry table is not committed: run `pnpm run write:indexes`, then open `INDEX.local.md` in this directory.
|
package/skills/tacitt/SKILL.md
CHANGED
|
@@ -14,7 +14,7 @@ Prepare the [exact paired CLI](references/bootstrap.md), then follow the [classi
|
|
|
14
14
|
|
|
15
15
|
Before initializing the run, inspect the native host's actually available subagent models. Select the newest available supported native fast-tier model at explicit `low` reasoning, using fresh context for a newly delegated task; use the SAME exact model and effort for classification, bounded evidence, keyword/privacy scanning, internal card drafting and final resolution. Do not inherit the main agent's model or effort. On the currently documented Codex host this is `gpt-6-luna` with `reasoning_effort: "low"`; resolve availability again on a later host rather than assuming this dated example is universal. Use fresh-context children and pass the model/effort in the native spawn tool arguments, not just in a prompt. If the host cannot set the required profile, report that capability failure; do not silently switch model, raise effort or claim compliance.
|
|
16
16
|
|
|
17
|
-
Initialize the shared phase/review run with `--model EXACT_NATIVE_MODEL --effort low`. Keep at most
|
|
17
|
+
Initialize the shared phase/review run with `--model EXACT_NATIVE_MODEL --effort low`. Keep at most forty-eight native child agents active across all phases, further limited by actual available slots; a delegated coordinator also occupies a slot. Launch ready assignments up to available capacity together when the host supports parallel native spawns. Otherwise issue consecutive native spawns without waiting for results between launches. Every helper response returns the pinned `agentProfile`; use it for dispatch and record the actual spawn receipts and available slots. Helpers record requested settings, not proof that a model executed them. No global client configuration changes or extra API provider are needed.
|
|
18
18
|
|
|
19
19
|
## Coordinator ownership
|
|
20
20
|
|
|
@@ -22,6 +22,8 @@ The agent invoking this Skill is its Main coordinator, including when another ag
|
|
|
22
22
|
|
|
23
23
|
## Required order
|
|
24
24
|
|
|
25
|
+
Before first Work formation, `journey.prepare` reads the account inventory once, across all pages, with `include=title,removedFromWork,unitAccess` and the shared 30-second bound. A failed or partial read stops before classification and forms no Work. On `ACCOUNT_WORKS_UNAVAILABLE`, say "Tacitt could not read your profile, so it cannot tell which Works are already Private there." On `ACCOUNT_SIGNED_OUT`, ask the person to sign in. The helper's `accountReadRecovery` offers Retry only: get the current journey revision and submit `{kind:"account-read"}` once, then wait if the read still fails. For the first formation of a journey, never continue classification without a successful read or offer to group without the profile. A journey that already has a formed snapshot from an earlier build continues under its existing rules with no formation read. Consent without a subject keeps the existing signed-out exception. A successful read stores account facts for the later rebuild slice; it does not yet change Work ids, titles, grouping, access or Send.
|
|
26
|
+
|
|
25
27
|
For scan starts and reruns after scope confirmation, follow the [paired CLI background preparation, restart budget and prepare-outcome rules](references/commands.md#scan-results).
|
|
26
28
|
|
|
27
29
|
The person decides in the panel; you keep the journey moving. Never ask the person to return to chat to confirm or continue, except for the explicit [remote-worker host consent](references/remote-workers.md) before classification freezes. Until Ready to send, while the next step is theirs, [wait inside this turn](references/journey.md#waiting-for-the-person); at Ready to send your turn ends.
|
|
@@ -9,8 +9,8 @@ INDEX.md | Directory index | Locates the shipped native journey references.
|
|
|
9
9
|
b-method.md | Method reference | Defines the dictionary method: analysed scope, the child answer, the ten term types and how the hidden set is applied.
|
|
10
10
|
bootstrap.md | Skill reference | Verifies the exact paired CLI release and preserves installation boundaries.
|
|
11
11
|
classification.md | Skill reference | Owns priority dispatch of unresolved/evidence tasks, authoritative task-pointer prompts, semantic evidence safeguards, bounded reconciliation, final metadata and full-coverage publication and unconfirmed Public suggestions.
|
|
12
|
-
commands.md | Command reference | Supplies classification assignment and answer-publication, session review, decision-card, final-resolution, phase-helper, batched reader lifecycle events, explicit paired-CLI scan reruns, account inheritance, person-only Public choices and Send contracts.
|
|
13
|
-
journey.md | Skill reference | Guides explicit consent, row-assigned classification, CLI-owned answer publication and same-child recovery, account-seeded scan scope, explicit Public choices, contextual cards, final resolution, preview and approved packaging.
|
|
12
|
+
commands.md | Command reference | Supplies classification assignment and answer-publication, session review, decision-card, final-resolution, phase-helper, batched reader lifecycle events, explicit paired-CLI scan reruns, Retry-only formation reads, account inheritance, person-only Public choices and Send contracts.
|
|
13
|
+
journey.md | Skill reference | Guides explicit consent, row-assigned classification, CLI-owned answer publication and same-child recovery, Retry-only formation reads, account-seeded scan scope, explicit Public choices, contextual cards, final resolution, preview and approved packaging.
|
|
14
14
|
parallel-review.md | Review protocol | Runs the review helper loop: bounded current-provider analysis agents, CLI-published drafts, same-child repair, batched launches and exits, busy slots, durable replay and recovery.
|
|
15
15
|
remote-workers.md | Collection protocol | Owns per-Send host consent, bounded remote worker import before freeze and disclosure of unreadable dispatch lines or collection failures.
|
|
16
16
|
send.md | Send reference | Owns browser Send, owner login, scope confirmation, incremental session intent, concurrent transfers with serialized commits, narrow pre-upload access blocking, upload-bound completion, background assessment, fresh review recovery and deliberate upgrades.
|
|
@@ -110,7 +110,7 @@ the intended Skill; do not report native-client discovery without observing it.
|
|
|
110
110
|
|
|
111
111
|
## Client concurrency setup
|
|
112
112
|
|
|
113
|
-
The installer offers one default-No terminal question to save a setting of
|
|
113
|
+
The installer offers one default-No terminal question to save a setting of forty-eight
|
|
114
114
|
concurrent subagents for the selected clients. It explains speed, concurrent usage,
|
|
115
115
|
the user/project scope and restart. Explicit `--configure-agents` (JSON
|
|
116
116
|
`configureAgents:true`) authorizes the same change; `--skip-agent-config` declines.
|
|
@@ -73,6 +73,8 @@ Publish accepted completed-root progress at helper checkpoints while refilling s
|
|
|
73
73
|
|
|
74
74
|
After scope confirmation, one item may hold roots of one published Work, plus roots still being grouped. It may join several published Works only when they have the same visibility record, as a Work and the blocked Work split from it do. Otherwise the helper stops with `CLASSIFICATION_LATE_SEVERAL_WORKS` before commit (CLI would refuse with `INVALID_SELECTION`) and prints one line per refused item, such as `items[0] joins Works <id>, <id>`, because the joined roots would take the first Work's visibility, Public included. Keep each published Work's roots in its own item, and keep any other root pending or in a new item.
|
|
75
75
|
|
|
76
|
+
A rebuilt profile Work (`accountWork: true`, shown with its id and profile title) is a join target only through the catalog: a known project whose catalog entry carries `accountWorkId` is submitted by the helper as `{accountWorkId, rootSessionIds}`, with no description, project type or recommendation, and gets no description task (ADR 0276 slice H). Never name a rebuilt Work yourself and never put its roots in an item. The helper checks joins before commit and stops with `CLASSIFICATION_ACCOUNT_WORK_JOIN`, printing `items[<index>] joins Works <id>` per refused item; CLI also refuses a joined session the profile already lists (`INVALID_SELECTION`). Do not retry such a refusal by hand: report it and stop the classification step. Joining keeps the Work's title and visibility; a Public profile Work becomes a Public suggestion again for the person to confirm.
|
|
77
|
+
|
|
76
78
|
## One-shot workflow acceptance
|
|
77
79
|
|
|
78
80
|
The human-approved acceptance bar is a complete Skill workflow with useful
|
|
@@ -25,6 +25,7 @@ Consent arrives from the panel's Continue button (or, after an explicit chat agr
|
|
|
25
25
|
- `journey.classificationSubmit`: `{journeyId,expectedRevision,idempotencyKey,items}`. Each item is `{projectName,description,projectType,rootSessionIds,recommendation:{access,reason}}`. Types are `work`, `side`, `coursework`, `personal`, `scratch`, `other`; access is `public`, `private`, `drop`. Cover every root exactly once, preserve families and merge matching project identities; matching names alone are not identity. Recommendations do not change access by themselves.
|
|
26
26
|
- Apply explicit visibility using `{kind:"work-access",workId,access}` or `{kind:"works-access",workIds,access}`. Browser choices are already committed; never overwrite them with an older recommendation.
|
|
27
27
|
- Before `scope-confirm`, an unchosen Work may store a placeholder `access`; the panel shows its recommendation. Scope confirmation preserves confirmed person choices and a delegate's Private choice. It seeds account access while signed in (`accessOrigin:"account"`): all Private confirms, every root held Public confirms with account proof, partial Public history stays a suggestion, and mixed starts Private with a visible conflict. No account history uses the model path. Model Public also stays `accessConfirmed:false`; model Private may confirm. The panel asks the person for every suggested-Public Work. A suggested Public Work requires that person's explicit visibility choice, including choosing the same displayed Public value. Never use delegation to confirm Public.
|
|
28
|
+
- Before first formation, `journey.prepare` performs the all-or-nothing account read. `ACCOUNT_WORKS_UNAVAILABLE` or `ACCOUNT_SIGNED_OUT` stops before classification with no Work formed. Get the current revision and submit `{kind:"account-read"}` once as Retry; the helper returns this sole choice in `accountReadRecovery`. If Retry still fails, wait for the profile read (or sign-in) to recover; never continue without the profile. A successful formation read changes no Work grouping or visibility in slice A.
|
|
28
29
|
- As soon as the known Works (an `ingest` result with `partial: true`) or the complete proposal is ingested, record `{kind:"scope-confirm"}` for scan scope. This does not confirm suggested Public (a Work whose every root session is already Public on the account is confirmed by that account history and needs no question); name each suggested-Public Work and ask the person to choose Public or Private, in the panel or conversation. `scope-confirm` reads the account inventory first (30 s bound). A failed read refuses it with `ACCOUNT_ACCESS_UNAVAILABLE`, and a signed-out read with `ACCOUNT_SIGNED_OUT`; each records `accountRead` and changes no Work, and only a later `scope-confirm` that reads the account confirms the scope (Retry; it decides nothing, so run it once without asking). A signed-out `scope-confirm` continues on the model path only when this journey has recorded consent without an account subject. That covers consent given before sign-in was required or in a build with no sign-in client. A calling process with no sign-in client cannot exempt consent that records a subject. After confirmation, `{kind:"account-read"}` reads the account again from the panel or chat: on a read it re-applies account access to Public Works while preserving confirmed person choices, every Private Work and every left-out Work; on a failure it records the status only. The view's `accountReadCurrent` is true only for a `read` under the account signed in now; while it is false and an included Work is suggested Public or Public by account proof, the panel shows a stop with Retry or Sign in in place of the Send button. Then `journey.prepare` runs selected Work static scans and, while grouping continues, waits for and prepares the Works the complete proposal publishes or extends. `journey.classificationSubmit` takes optional `pendingRootSessionIds` (roots still being grouped, kept in one unread Work) and, after confirmation, `expectedClassificationRevision` (the classification it completes) instead of a current `expectedRevision`. Visibility stays open until Send: Drop stops that Work's scan, and a Work moved out of Drop needs `journey.prepare` again.
|
|
29
30
|
|
|
30
31
|
## Remote worker collection
|
|
@@ -57,6 +58,19 @@ Ingest and `next-packs` list `blockedWorks[]` only while one exists.
|
|
|
57
58
|
Phase-helper `status` and `await` also print `leftOutSessions`, the number of root sessions in
|
|
58
59
|
those Works, only when it is above zero; say it in chat before the review is complete, Ready
|
|
59
60
|
to send or nothing is left to send.
|
|
61
|
+
They also print `accountFormation: {leftOutSessions, heldBackSessions}` when formation kept
|
|
62
|
+
sessions out of every Work because of the person's profile (ADR 0276). Say each nonzero count
|
|
63
|
+
once in the journey, never a session or Work name: "N sessions are left out because you
|
|
64
|
+
removed their Work on your profile." and "N sessions stay in their Works on your profile and
|
|
65
|
+
are not sent this time." (singular: "1 session is left out because you removed its Work on
|
|
66
|
+
your profile.", "1 session stays in its Work on your profile and is not sent this time.").
|
|
67
|
+
When every session is already on the profile, left out or held back, `journey.prepare` fails with
|
|
68
|
+
`ACCOUNT_NOTHING_TO_SEND` and the helper phase is `nothing-to-send` with `accountFormation`: say
|
|
69
|
+
the counts once and that nothing is left to send this time, then end the turn. Running `/tacitt`
|
|
70
|
+
again reads the profile again.
|
|
71
|
+
Works with `accountWork: true` are the person's profile Works rebuilt with their profile title;
|
|
72
|
+
classification never groups or describes them. A new session whose project already has such a
|
|
73
|
+
Work joins it instead of forming another (ADR 0276 slice H): see the classification reference.
|
|
60
74
|
When no selected Work is left (every one skipped or Dropped), the helper phase is
|
|
61
75
|
`nothing-to-send`: there is no Send, and `await` waits for a Retry or a move out of Drop.
|
|
62
76
|
When the person asks to retry, respond
|
|
@@ -172,11 +186,11 @@ exhausted command budget or unknown write stops refill. Known repairs and partia
|
|
|
172
186
|
`next-packs --agents FREE_SLOTS` remains available. Refill observes current input
|
|
173
187
|
and guidance again; accepted results remain bound to existing source receipts.
|
|
174
188
|
|
|
175
|
-
For native orchestration, use automatic refill only when all
|
|
189
|
+
For native orchestration, use automatic refill only when all forty-eight host slots are free with no pending launches. While any child is live, wait on native completion and pass the current numeric free count. An answer can appear before its worker exits.
|
|
176
190
|
|
|
177
191
|
Aggregate `ingest` defaults to automatic refill. `review.mjs ingest --run PRIVATE_RUN --agents auto --wait-ms 30000`
|
|
178
192
|
ingests published answers, then reserves packs for every free slot, where free
|
|
179
|
-
slots are
|
|
193
|
+
slots are forty-eight minus outstanding scan packs, unacknowledged lost children,
|
|
180
194
|
active card children and due-card reservations (reported as `freeSlots`). When nothing was ingested or reserved, it waits up to `--wait-ms`
|
|
181
195
|
(bounded by half the command budget) for a published answer, rechecking journey
|
|
182
196
|
input every few seconds, and returns as soon as something changes. Within that wait
|
|
@@ -198,7 +212,7 @@ within confirmed available native capacity and calls again, until `next` is
|
|
|
198
212
|
AI agent slots and static CPU workers are separate resources: background static
|
|
199
213
|
processing (Track B), its progress and the core count never reduce `freeSlots`.
|
|
200
214
|
Auto mode subtracts `--card-agents N` active card children as well as outstanding
|
|
201
|
-
privacy packs and unacknowledged lost children from
|
|
215
|
+
privacy packs and unacknowledged lost children from forty-eight. When a card batch
|
|
202
216
|
is due, `--planned-card-agents N` (default one) reserves upcoming card children
|
|
203
217
|
before scan refill; `cardBatch.plannedAgents` reports the reservation available
|
|
204
218
|
within capacity. Dispatch at most that count in the same turn as scan packs and
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Classification-first native journey
|
|
2
2
|
|
|
3
|
+
Before first Work formation, `journey.prepare` reads the account inventory once, across all pages, with `include=title,removedFromWork,unitAccess` and the shared 30-second bound. A failed or partial read stops before classification and forms no Work. On `ACCOUNT_WORKS_UNAVAILABLE`, say "Tacitt could not read your profile, so it cannot tell which Works are already Private there." On `ACCOUNT_SIGNED_OUT`, ask the person to sign in. The helper's `accountReadRecovery` offers Retry only: get the current journey revision and submit `{kind:"account-read"}` once, then wait if the read still fails. For the first formation of a journey, never continue classification without a successful read or offer to group without the profile. A journey that already has a formed snapshot from an earlier build continues under its existing rules with no formation read. Consent without a subject keeps the existing signed-out exception. A successful read stores account facts for the later rebuild slice; it does not yet change Work ids, titles, grouping, access or Send.
|
|
4
|
+
|
|
3
5
|
> Audience: native Codex and Claude Code agents
|
|
4
6
|
> Ownership: Tacitt CLI maintainers
|
|
5
7
|
|
|
@@ -25,9 +27,9 @@ A concise stage card explains the current next step and Public/Private/Drop mean
|
|
|
25
27
|
|
|
26
28
|
## Scan
|
|
27
29
|
|
|
28
|
-
Run `journey.prepare` after scope confirmation to perform static processing only for selected Works; while grouping continues it waits and also prepares the Works the complete proposal publishes or extends. Use the compact `result:"receipt"` response for this owned preparation. Keep this operation running and use `review.mjs next-packs` as each session publishes authenticated static receipts and review input, without waiting for the remaining sessions in its Work or creating ZIPs. Dispatch printed prompts verbatim, ingest completed answers and refill available slots within the shared
|
|
30
|
+
Run `journey.prepare` after scope confirmation to perform static processing only for selected Works; while grouping continues it waits and also prepares the Works the complete proposal publishes or extends. Use the compact `result:"receipt"` response for this owned preparation. Keep this operation running and use `review.mjs next-packs` as each session publishes authenticated static receipts and review input, without waiting for the remaining sessions in its Work or creating ZIPs. Dispatch printed prompts verbatim, ingest completed answers and refill available slots within the shared forty-eight-child ceiling across phases. The helper saves task files and repeatable batched requests; do not replace it with custom scripts.
|
|
29
31
|
|
|
30
|
-
At the first ready unit and each completion, run the [shared dispatch loop](parallel-review.md#dispatch-while-stages-overlap). Pass the actual free count to `review.mjs ingest --run PRIVATE_RUN --agents FREE_SLOTS --card-agents ACTIVE_CARDS` while any child is live or the host supplies fewer than
|
|
32
|
+
At the first ready unit and each completion, run the [shared dispatch loop](parallel-review.md#dispatch-while-stages-overlap). Pass the actual free count to `review.mjs ingest --run PRIVATE_RUN --agents FREE_SLOTS --card-agents ACTIVE_CARDS` while any child is live or the host supplies fewer than forty-eight slots. Automatic capacity is for a verified empty pool with all forty-eight slots free. Spawn one fresh native child per returned `packs[].prompt` using the pinned `agentProfile`; then check both queues again. When the only runnable work is already in flight, wait for a native completion for up to 30 seconds, then recount free host slots before ingesting. Background static processing uses separate CPU workers. Follow returned `ingested[].prompt`, `retryPrompt` or repair instructions before calling again. Bind every launched reader and report verified closure through the [reader recovery contract](parallel-review.md#recovery). Lost readers retain their slots until Main confirms closure. Accepted writes and pending repairs keep their recovery authority. A confirmed technical failure may release the remaining batches for a fresh attempt. Refused units remain visibly incomplete and receive no new reader. Launch independent work first, then handle ongoing repair. When `cardBatch.ready` is true, dispatch at most `cardBatch.plannedAgents` [card drafting](#decision-cards) children in the same turn as scan refills, queueing the rest. The helper reserves one planned card slot by default; request more with `--planned-card-agents N` before scan refill. Ingest with the actual remaining capacity immediately after dispatch and between bounded Main-owned reconciliation steps; do not wait for the whole card batch before refilling scans. While `cardBatch.ready` is false, keep dispatching packs even when `cardBatch.uncovered` grows. Handle `done` and `paused` at their normal boundaries. At `report-blocked`, report the unresolved units and follow the panel decision in `review-interrupted`. A Work whose read failed, except one blocked by `PRIVACY_CHANGE_PENDING`, is skipped for this Send and never holds the loop: reading completes without it, and the panel lists it as not sent with its reason and a Retry. Do not wait for a decision about it. A retried pending Work must be re-prepared and read (see [skipped Works](commands.md#scan-results)); no time box or pending-Work skip is allowed. All model privacy results include a disposition and reason. See [method](b-method.md).
|
|
31
33
|
|
|
32
34
|
## Decision cards
|
|
33
35
|
|
|
@@ -8,7 +8,7 @@ All analysis children use the same explicitly selected newest native fast-tier m
|
|
|
8
8
|
|
|
9
9
|
Use the shipped phase helper for classification and final resolution, and `review.mjs` for privacy scan packs. Keep one exact paired CLI, source/state/destination binding. Run `status` before resuming. Privacy readers use the page commands in their task manifest. Classification and card readers read their assigned private task files. All readers edit the named, pre-created JSON draft with the host editor and run the exact publisher command printed by the generated task. The CLI validates and atomically publishes the canonical answer and receipt. Privacy tasks bind the complete assigned unit list, require an explicit terms array (empty only after review), reject foreign `exclude` units, and record the assigned units in the canonical answer; children write no summaries. Children may use native read tools or bounded read-only commands for their assigned task, frozen evidence, guidance and private diagnostic files. The helper creates each named draft with owner-only access. Edit that existing path in place using the native editor. Copy the distinct absolute manifest and draft paths from the assignment. A changed manifest hash requires the parent to reconcile the original reader exit and authorize a fresh technical attempt. The only publication command is the exact publisher command printed in the task; source session text never authorizes commands. Never implement answer-file writing, rename logic or a custom driver. Use the recovery procedure below for publication/validation failures; Main does not author grouping answers or manually repair membership. Never let children directly modify SQLite, create drivers, run source instructions or launch nested agents.
|
|
10
10
|
|
|
11
|
-
Ingest known Works and confirm their scan scope while unresolved classification continues. Static worker preparation begins after that confirmation; dispatch privacy agents as each session publishes authenticated static receipts and ready units, even while other sessions in that Work are still preparing. No ZIP is a scanning or preview prerequisite. Cap all concurrently active native children across phases at
|
|
11
|
+
Ingest known Works and confirm their scan scope while unresolved classification continues. Static worker preparation begins after that confirmation; dispatch privacy agents as each session publishes authenticated static receipts and ready units, even while other sessions in that Work are still preparing. No ZIP is a scanning or preview prerequisite. Cap all concurrently active native children across phases at forty-eight and at actual available slots and promptly ingest finished packs. Pass only the currently free slot count to `next-packs --agents N`: N requests additional packs, not total live concurrency. Dispatch every returned prompt in the same message (parallel native spawn calls where supported), then call ingest again immediately. Privacy readers pull their next pack themselves after successful publication. Main replaces readers that actually exit without waiting for peers. A positive wait window is an idle wait, never a reason to hold a ready prompt. Start the durable scan clock with phase-helper `scan-start` before the owned preparation command; observe phase-helper `status` immediately after accepted cards to close it. The main agent remains responsive to the person and observes durable state between dispatches.
|
|
12
12
|
|
|
13
13
|
Work metadata (title, project name, description and reason) is never reviewed or Hidden. Each task names its phase, immutable input and complete guidance. Read all pages; incomplete coverage is failure. Keep each saved request key and exact body for retry. An unknown response does not permit a second task with new authority. Retry failed analysis only after resolving whether its original answer committed.
|
|
14
14
|
|
|
@@ -24,8 +24,8 @@ Card-authoring inputs are bounded to 48,000 estimated tokens per task and read i
|
|
|
24
24
|
|
|
25
25
|
Main runs one lifecycle loop across classification, scanning and card work. It launches the initial privacy pool together; persistent privacy readers pull successive assignments without Main dispatch between packs. Main binds any missing native receipts, replaces actual exits and handles cards. On each native completion, new input or preparation milestone, check receipts and ready tasks. Keep the existing classification priority for unresolved roots, requested evidence and reconciliation. Admit known Works as soon as their metadata is published. Continue launching ready privacy packs while grouping or its repairs run.
|
|
26
26
|
|
|
27
|
-
1. Count native capacity across the whole tree, including a delegated Main, other live children and reservations awaiting launch. Use the smaller of the host's available slots and the space left under
|
|
28
|
-
2. After allocating slots to other stage tasks, pass the remaining free count to `review.mjs ingest --agents FREE_SLOTS --card-agents ACTIVE_CARDS`. This single call records batched reader events, ingests and refills. Pass `--reader-starts JSON` only for native spawn receipts not already self-bound by readers, and `--reader-exits JSON` for confirmed exits since the last call; starts are applied before exits. Each array contains objects with `packId`, `readerId` (the native ID), optional `turn`, and, for exits, `result` (`published`, `retry` or `refused`). Use zero for ingestion only. Bare aggregate `ingest` and `--agents auto` infer capacity; use them only when all
|
|
27
|
+
1. Count native capacity across the whole tree, including a delegated Main, other live children and reservations awaiting launch. Use the smaller of the host's available slots and the space left under forty-eight. Maintain one owner per task; publication alone does not prove child exit. Static preparation uses separate CPU workers.
|
|
28
|
+
2. After allocating slots to other stage tasks, pass the remaining free count to `review.mjs ingest --agents FREE_SLOTS --card-agents ACTIVE_CARDS`. This single call records batched reader events, ingests and refills. Pass `--reader-starts JSON` only for native spawn receipts not already self-bound by readers, and `--reader-exits JSON` for confirmed exits since the last call; starts are applied before exits. Each array contains objects with `packId`, `readerId` (the native ID), optional `turn`, and, for exits, `result` (`published`, `retry` or `refused`). Use zero for ingestion only. Bare aggregate `ingest` and `--agents auto` infer capacity; use them only when all forty-eight native slots are confirmed free and no launch is outstanding. While any child is live, use a bounded native completion wait, then recount and pass the numeric free count. Never infer a free slot from the configured maximum alone.
|
|
29
29
|
3. For a due batch, request ready card capacity with `--planned-card-agents N`. The default reserves one slot. Open the due batch promptly. Once it is open the helper stops reserving for a new batch. If it produces fewer unowned drafts than planned, run ingest again immediately with the actual remaining capacity. When card drafts and privacy packs are both ready, advance both in the same dispatch when capacity permits. Keep request counts within the ready card backlog and give surplus capacity to ready work in the other stage. Launch at most `cardBatch.plannedAgents` card children alongside every returned privacy pack. On an already-open card batch, count its queued drafts explicitly before assigning remaining slots to scans. Recompute from the host at the next completion.
|
|
30
30
|
4. If the complete global card catalog needs substantial analysis while other work is ready, assign one fresh pinned child the read-only comparison of all candidates against `existingCards`. Give it only the task's bounded catalog, existing policies and output schema. Ask for a proposal referencing candidate IDs, without canonical publication, CLI mutations or panel actions. Count this child in capacity. Main keeps refilling, reviews the complete catalog and proposal in bounded portions between dispatches, verifies every mapping and policy, then solely publishes the global answer and ingests it. For an extend-only batch already ready for ingestion, ingest immediately.
|
|
31
31
|
5. Dispatch unowned ready work before tracing diagnostics, writing timing reports or waiting. Spawn all returned privacy packs and planned card children in the same message, using parallel native spawn calls where supported. Readers bind their own actual native ID with the generated `reader-start` as their first command when the host exposes self identity. Collect any remaining spawn receipts and all confirmed exits into the next ingest call; do not spend a separate coordinator turn per child start or exit. Run ingest again without waiting after the dispatch batch and when children exit. When no stage child is active and owned preparation is the only producer, pass the positive actual host budget and `--wait-ms 30000` to the same ingest call. Repeated probes inside that wait reuse one authenticated CLI runtime. When stage children are active, wait up to 30 seconds for the first change; return to both queues immediately.
|
|
@@ -16,11 +16,11 @@ journey-classification-metadata.d.mts | Declaration | Types generated AI metadat
|
|
|
16
16
|
journey-classification-metadata.mjs | Runtime | Validates global reconciliation, reduces oversized Works from all evidence slices and creates hash-bound, token-bounded AI metadata tasks for new Works and Unsorted.
|
|
17
17
|
journey-classification-progress.mjs | Runtime | Accepted-answer counters and fenced progress checkpoints.
|
|
18
18
|
journey-classification-tasks.d.mts | Declaration | Types initial classification routing and catalog/evidence task generation for helper consumers and tests.
|
|
19
|
-
journey-classification-tasks.mjs | Runtime | Original-root routing, capacity-balanced bounded tasks and unresolved-first ready ordering with stable assignments.
|
|
19
|
+
journey-classification-tasks.mjs | Runtime | Original-root routing, capacity-balanced bounded tasks and unresolved-first ready ordering with stable assignments; known projects the catalog ties to a rebuilt Work become joins, not tasks.
|
|
20
20
|
journey-classification-validation.d.mts | Declaration | Shared classification validator contracts.
|
|
21
|
-
journey-classification-validation.mjs | Runtime | Shared root judgment, stable target identity, fixed project metadata, consolidation validation, target-preserving assignment expansion and the late check that an item joins only placed Works with one access record (ADR 0274).
|
|
21
|
+
journey-classification-validation.mjs | Runtime | Shared root judgment, stable target identity, fixed project metadata, consolidation validation, target-preserving assignment expansion and the late check that an item joins only placed Works with one access record (ADR 0274), and the account Work join check (ADR 0276 slice H).
|
|
22
22
|
journey-classification.mjs | Runtime | Batched known-project metadata, unknown-root grouping and streamed four-row evidence, versioned checkpoints and generation progress.
|
|
23
|
-
journey-consolidation-submit.mjs | Runtime | Expands candidate assignments, applies per-root overrides, combines small topics into Unsorted, attaches known projects and checks exact root coverage.
|
|
23
|
+
journey-consolidation-submit.mjs | Runtime | Expands candidate assignments, applies per-root overrides, combines small topics into Unsorted, attaches known projects, adds account Work join items (ADR 0276 slice H) and checks exact root coverage.
|
|
24
24
|
journey-consolidation.d.mts | Declaration | Consolidation helper contract for bounded evidence regression tests.
|
|
25
25
|
journey-consolidation.mjs | Runtime | Builds proposed-name and stable-target candidate buckets, persists complete reconciliation input, and returns compact dispatch descriptors.
|
|
26
26
|
journey-panel-host.d.mts | Declaration | Types the detached-host handoff for helper tests.
|
|
@@ -32,13 +32,13 @@ journey-state.mjs | Runtime state | Pending-Public-choice, confirmed-answer and
|
|
|
32
32
|
journey-tasks.d.mts | Declaration | Types task creation, rendered-token sizing, canonical answer metadata and batch answer reads.
|
|
33
33
|
journey-tasks.mjs | Task builder | Exact rendered-token budgets, private drafts, a 200-value cap (ADR 0234), compact existing policies with bounded overhead (ADR 0251), and one-term-per-line card/resolution payloads with complete bounded read ranges.
|
|
34
34
|
journey.d.mts | Declaration | Phase helper contract for integration tests.
|
|
35
|
-
journey.mjs | Runtime |
|
|
35
|
+
journey.mjs | Runtime | Retry-only formation failure/status projection and phase tasks with compact existing-card identity snapshots, durable exact batch writes, the person-decision `await` that hands off at Ready to send, the late placed-Work check before the classification commit (its stderr names only item indexes and Work ids) and shared CLI transport.
|
|
36
36
|
local-cli.d.mts | Type declarations | Defines the shared installed-package binding used by the CLI and standalone verifier.
|
|
37
37
|
local-cli.mjs | Read-only package verifier | Validates an explicit installed CLI identity and aggregate artifact digest without network or application-state access.
|
|
38
38
|
review-stream-client.d.mts | Type declarations | Types the fixed request/shutdown limits, private lifecycle callbacks and review-stream client methods.
|
|
39
39
|
review-stream-client.mjs | Runtime | Bounds requests and owned-child shutdown, permits retry only for explicit pre-request startup busy, and preserves unknown lost-write outcomes for receipt replay.
|
|
40
40
|
review.d.mts | Type declarations | Types the review helper's exported task rendering, answer reading, per-Work progress and entry functions for tests.
|
|
41
|
-
review.mjs | Runtime | Session-ready scanning, ready-pack-first
|
|
41
|
+
review.mjs | Runtime | Session-ready scanning, ready-pack-first forty-eight-slot refill, 30000-token pack target, manifest-derived page byte capture, default reader pull with native self-binding, host-capability fallback and replayable slot transfer, blocked-Work exclusion, largest-Work queue ordering, isolated retries, batched native reader and turn receipts with launch reservations, bounded reissue and visible interruption recovery, planned card capacity, outcome-reported preparation recovery and read-only status, metadata-gated questions, Track B-gated final preview and hash-bound answer recovery.
|
|
42
42
|
verify-cli.mjs | Verification entry | Checks a managed local receipt and its current package bytes before the Skill starts a journey.
|
|
43
43
|
-->
|
|
44
44
|
|
|
@@ -64,6 +64,12 @@ export async function publishClassificationProgress(
|
|
|
64
64
|
const knownKeys = new Set(classification.projects.flatMap((project) => project.rootKeys))
|
|
65
65
|
const aiCompleted = new Set()
|
|
66
66
|
const completed = new Set()
|
|
67
|
+
// Roots joining a rebuilt account Work are grouped by routing alone (ADR 0276 slice H).
|
|
68
|
+
for (const join of classification.accountJoins ?? [])
|
|
69
|
+
for (const key of join.rootKeys) {
|
|
70
|
+
knownKeys.add(key)
|
|
71
|
+
completed.add(key)
|
|
72
|
+
}
|
|
67
73
|
const latestJudgments = new Set()
|
|
68
74
|
for (const item of manifest.tasks) {
|
|
69
75
|
if (item.evidenceRound > 0 && !(await optional(item.answer))) continue
|
|
@@ -21,6 +21,8 @@ const projectInstruction =
|
|
|
21
21
|
const rootUniverse = (view) => {
|
|
22
22
|
const result = []
|
|
23
23
|
for (const work of view.works) {
|
|
24
|
+
// Rebuilt account Works are not classified (ADR 0276 D1).
|
|
25
|
+
if (work.accountWork) continue
|
|
24
26
|
for (const rootSessionId of work.rootSessionIds) {
|
|
25
27
|
result.push({ rootSessionId, workId: work.id })
|
|
26
28
|
}
|
|
@@ -114,9 +116,17 @@ async function initialize(directory, manifest, view, call) {
|
|
|
114
116
|
})
|
|
115
117
|
if (projectRootKeys.size + unknown.length !== roots.length)
|
|
116
118
|
throw new Error('CLASSIFICATION_ROUTING_COVERAGE_INVALID')
|
|
119
|
+
// ADR 0276 slice H: a known project the catalog ties to a rebuilt account Work joins it. It is
|
|
120
|
+
// neither described nor offered to the classifier as a target.
|
|
121
|
+
const accountJoins = projects.flatMap((project) =>
|
|
122
|
+
project.metadata.accountWorkId
|
|
123
|
+
? [{ accountWorkId: project.metadata.accountWorkId, rootKeys: project.rootKeys }]
|
|
124
|
+
: [],
|
|
125
|
+
)
|
|
117
126
|
manifest.classification = {
|
|
118
127
|
roots,
|
|
119
|
-
projects,
|
|
128
|
+
projects: projects.filter((project) => !project.metadata.accountWorkId),
|
|
129
|
+
accountJoins,
|
|
120
130
|
unknown,
|
|
121
131
|
routingAssignments: assignments,
|
|
122
132
|
evidence: [],
|
|
@@ -18,8 +18,21 @@ export function validateConsolidationAnswer(
|
|
|
18
18
|
projectCatalog: unknown[]
|
|
19
19
|
},
|
|
20
20
|
): { code: string | null; details: string[] }
|
|
21
|
+
export function validateAccountWorkJoins(
|
|
22
|
+
items: ReadonlyArray<{ rootSessionIds: readonly string[]; accountWorkId?: string | undefined }>,
|
|
23
|
+
works: ReadonlyArray<{
|
|
24
|
+
id: string
|
|
25
|
+
placement?: string
|
|
26
|
+
rootSessionIds: readonly string[]
|
|
27
|
+
accountWork?: boolean
|
|
28
|
+
}>,
|
|
29
|
+
confirmed: boolean,
|
|
30
|
+
): void
|
|
21
31
|
export function validateLatePlacedWorks(
|
|
22
|
-
items: ReadonlyArray<
|
|
32
|
+
items: ReadonlyArray<
|
|
33
|
+
| { projectName: string; rootSessionIds: readonly string[] }
|
|
34
|
+
| { accountWorkId: string; rootSessionIds: readonly string[] }
|
|
35
|
+
>,
|
|
23
36
|
works: ReadonlyArray<{
|
|
24
37
|
id: string
|
|
25
38
|
placement?: string
|
|
@@ -257,6 +257,8 @@ export function validateLatePlacedWorks(items, works) {
|
|
|
257
257
|
const details = []
|
|
258
258
|
const conflicts = []
|
|
259
259
|
items.forEach((item, index) => {
|
|
260
|
+
// Account Work joins are checked by `validateAccountWorkJoins`.
|
|
261
|
+
if (Object.hasOwn(item, 'accountWorkId')) return
|
|
260
262
|
const owners = placed.filter((work) =>
|
|
261
263
|
work.rootSessionIds.some((root) => item.rootSessionIds.includes(root)),
|
|
262
264
|
)
|
|
@@ -276,3 +278,46 @@ export function validateLatePlacedWorks(items, works) {
|
|
|
276
278
|
conflicts,
|
|
277
279
|
})
|
|
278
280
|
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* ADR 0276 slice H, mirroring `createClassificationProposal` in the CLI, which stays
|
|
284
|
+
* authoritative: a join item has exactly `accountWorkId` and `rootSessionIds` and names a rebuilt
|
|
285
|
+
* Work; after scope confirmation it takes only roots still being grouped; no item takes a root of
|
|
286
|
+
* a rebuilt Work. Whether the account lists a joined root's keys only the CLI can check.
|
|
287
|
+
*/
|
|
288
|
+
export function validateAccountWorkJoins(items, works, confirmed) {
|
|
289
|
+
const rebuilt = works.filter((work) => work.accountWork)
|
|
290
|
+
const grouping = new Set(
|
|
291
|
+
works.flatMap((work) => (work.placement === 'classifying' ? work.rootSessionIds : [])),
|
|
292
|
+
)
|
|
293
|
+
const details = []
|
|
294
|
+
const conflicts = []
|
|
295
|
+
items.forEach((item, index) => {
|
|
296
|
+
const holders = rebuilt.filter((work) =>
|
|
297
|
+
work.rootSessionIds.some((root) => item.rootSessionIds.includes(root)),
|
|
298
|
+
)
|
|
299
|
+
const join = Object.hasOwn(item, 'accountWorkId')
|
|
300
|
+
const problem = holders.length
|
|
301
|
+
? 'takes roots of a rebuilt Work'
|
|
302
|
+
: !join
|
|
303
|
+
? null
|
|
304
|
+
: Object.keys(item).some((field) => !['accountWorkId', 'rootSessionIds'].includes(field))
|
|
305
|
+
? 'joins with fields other than accountWorkId and rootSessionIds'
|
|
306
|
+
: !rebuilt.some((work) => work.id === item.accountWorkId)
|
|
307
|
+
? 'names a Work that is not a rebuilt account Work'
|
|
308
|
+
: confirmed && item.rootSessionIds.some((root) => !grouping.has(root))
|
|
309
|
+
? 'joins roots that are no longer being grouped'
|
|
310
|
+
: null
|
|
311
|
+
if (!problem) return
|
|
312
|
+
const workIds = holders.length ? holders.map((work) => work.id) : [item.accountWorkId]
|
|
313
|
+
conflicts.push({ item: index, workIds })
|
|
314
|
+
details.push(`items[${index}] ${problem}`)
|
|
315
|
+
})
|
|
316
|
+
// `conflicts` carries only item indexes and Work ids, so the helper may print it to stderr.
|
|
317
|
+
if (details.length)
|
|
318
|
+
throw Object.assign(new Error('CLASSIFICATION_ACCOUNT_WORK_JOIN'), {
|
|
319
|
+
code: 'CLASSIFICATION_ACCOUNT_WORK_JOIN',
|
|
320
|
+
details,
|
|
321
|
+
conflicts,
|
|
322
|
+
})
|
|
323
|
+
}
|
|
@@ -50,10 +50,23 @@ async function knownProjects(classification, manifest) {
|
|
|
50
50
|
return result
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/** ADR 0276 slice H: join items, with no description, project type or recommendation (Q6). */
|
|
54
|
+
function accountJoinItems(classification) {
|
|
55
|
+
return (classification.accountJoins ?? []).map((join) => ({
|
|
56
|
+
accountWorkId: join.accountWorkId,
|
|
57
|
+
rootSessionIds: join.rootKeys.map((key) => {
|
|
58
|
+
const root = classification.roots.find((entry) => entry.key === key)
|
|
59
|
+
if (!root) throw new Error('CLASSIFICATION_ROOT_COVERAGE_INVALID')
|
|
60
|
+
return root.rootSessionId
|
|
61
|
+
}),
|
|
62
|
+
}))
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
/**
|
|
54
66
|
* E2: once every known project has its metadata, its Works are published while the unresolved
|
|
55
67
|
* roots are still being grouped; they wait as `pendingRootSessionIds`. Null while metadata is
|
|
56
|
-
* missing or when nothing remains to group.
|
|
68
|
+
* missing or when nothing remains to group. Roots joining a rebuilt Work also wait, so the
|
|
69
|
+
* complete proposal joins them once from the pending Work (ADR 0276 slice H).
|
|
57
70
|
*/
|
|
58
71
|
export async function partialClassificationSubmission(manifest) {
|
|
59
72
|
const classification = manifest.classification
|
|
@@ -67,8 +80,11 @@ export async function partialClassificationSubmission(manifest) {
|
|
|
67
80
|
if (!(await optional(item.answer))) return null
|
|
68
81
|
const known = await knownProjects(classification, manifest)
|
|
69
82
|
const rootByKey = new Map(classification.roots.map((root) => [root.key, root.rootSessionId]))
|
|
70
|
-
const pendingRootSessionIds =
|
|
71
|
-
|
|
83
|
+
const pendingRootSessionIds = [
|
|
84
|
+
...classification.unknown.map((item) => item.rootKey),
|
|
85
|
+
...(classification.accountJoins ?? []).flatMap((join) => join.rootKeys),
|
|
86
|
+
].map((key) => {
|
|
87
|
+
const id = rootByKey.get(key)
|
|
72
88
|
if (!id) throw new Error('CLASSIFICATION_ROOT_COVERAGE_INVALID')
|
|
73
89
|
return id
|
|
74
90
|
})
|
|
@@ -192,10 +208,11 @@ export async function classificationSubmission(manifest) {
|
|
|
192
208
|
})
|
|
193
209
|
}
|
|
194
210
|
}
|
|
211
|
+
const output = [...normalized(result), ...accountJoinItems(classification)]
|
|
195
212
|
const expectedRootIds = classification.roots
|
|
196
213
|
.map((root) => root.rootSessionId)
|
|
197
214
|
.toSorted((left, right) => left.localeCompare(right))
|
|
198
|
-
const submittedRootIds =
|
|
215
|
+
const submittedRootIds = output
|
|
199
216
|
.flatMap((item) => item.rootSessionIds)
|
|
200
217
|
.toSorted((left, right) => left.localeCompare(right))
|
|
201
218
|
if (
|
|
@@ -203,7 +220,7 @@ export async function classificationSubmission(manifest) {
|
|
|
203
220
|
JSON.stringify(expectedRootIds) !== JSON.stringify(submittedRootIds)
|
|
204
221
|
)
|
|
205
222
|
throw new Error('CLASSIFICATION_ROOT_COVERAGE_INVALID')
|
|
206
|
-
return
|
|
223
|
+
return output
|
|
207
224
|
}
|
|
208
225
|
|
|
209
226
|
/** Trimmed submission items; one project name never carries two different metadata sets. */
|
|
@@ -198,7 +198,7 @@ export async function observeScanTiming(run, observation) {
|
|
|
198
198
|
if (!receipt || receipt.completedAt) return
|
|
199
199
|
const at = observation.at
|
|
200
200
|
const measurements = receipt.measurements ?? {
|
|
201
|
-
slots: { capacity:
|
|
201
|
+
slots: { capacity: 48, busyChildMs: 0, waitingBatchWallMs: 0, refillGapMs: [], children: [] },
|
|
202
202
|
preparation: {
|
|
203
203
|
intervals: [],
|
|
204
204
|
milliseconds: { 'before-first-batch': 0, 'mid-run': 0, tail: 0 },
|
|
@@ -44,6 +44,9 @@ export function isSkipped(
|
|
|
44
44
|
work: Pick<SkipRuleWork, 'status' | 'blockedCodes' | 'inputRevision' | 'reviewInterruptions'>,
|
|
45
45
|
): boolean
|
|
46
46
|
export function isIncluded(work: SkipRuleWork): boolean
|
|
47
|
+
export function accountFormationField(view: {
|
|
48
|
+
accountWorks?: { status: string; leftOut?: number; heldBack?: number }
|
|
49
|
+
}): { accountFormation?: { leftOutSessions: number; heldBackSessions: number } }
|
|
47
50
|
export function leftOutField(view: { works: readonly SkipRuleWork[] }): {
|
|
48
51
|
leftOutSessions?: number
|
|
49
52
|
}
|
|
@@ -69,6 +69,13 @@ export const isSkipped = (work) =>
|
|
|
69
69
|
* Issue #59: the sessions (roots) of every skipped, not Dropped Work, which the agent says in chat
|
|
70
70
|
* before Ready to send. One number, printed only when above zero (issue #55).
|
|
71
71
|
*/
|
|
72
|
+
/** ADR 0276 D1 formation counts, as `accountFormationLines` of the panel; tell the person once. */
|
|
73
|
+
export const accountFormationField = (view) => {
|
|
74
|
+
const read = view.accountWorks?.status === 'read' ? view.accountWorks : null
|
|
75
|
+
return read && (read.leftOut || read.heldBack)
|
|
76
|
+
? { accountFormation: { leftOutSessions: read.leftOut, heldBackSessions: read.heldBack } }
|
|
77
|
+
: {}
|
|
78
|
+
}
|
|
72
79
|
export const leftOutField = (view) => {
|
|
73
80
|
const sessions = view.works
|
|
74
81
|
.filter((work) => work.access !== 'drop' && isSkipped(work))
|
|
@@ -120,7 +127,7 @@ export function phaseOf(view, drainedRevision) {
|
|
|
120
127
|
if (view.phase === 'paused') return 'paused'
|
|
121
128
|
if (view.phase === 'completed') return 'completed'
|
|
122
129
|
if (view.phase === 'sending') return 'sending'
|
|
123
|
-
if (!view.snapshotId) return 'freeze'
|
|
130
|
+
if (!view.snapshotId) return view.accountWorks?.nothingToSend ? 'nothing-to-send' : 'freeze'
|
|
124
131
|
if (view.classificationRevision === null) return 'classification'
|
|
125
132
|
if (view.scope.status !== 'confirmed') return 'classification-confirmation'
|
|
126
133
|
// A pending Hide needs journey.prepare to recover it before anything else (ADR 0236).
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
interruptedReviews,
|
|
13
13
|
isIncluded,
|
|
14
14
|
leftOutField,
|
|
15
|
+
accountFormationField,
|
|
15
16
|
activeTerms,
|
|
16
17
|
stageFingerprint,
|
|
17
18
|
procedureFor,
|
|
@@ -24,7 +25,10 @@ import {
|
|
|
24
25
|
uncoveredPending,
|
|
25
26
|
} from './journey-state.mjs'
|
|
26
27
|
import { classificationTasks } from './journey-classification-tasks.mjs'
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
validateAccountWorkJoins,
|
|
30
|
+
validateLatePlacedWorks,
|
|
31
|
+
} from './journey-classification-validation.mjs'
|
|
28
32
|
import { classificationEvidence, classificationFormat } from './journey-classification.mjs'
|
|
29
33
|
import {
|
|
30
34
|
consolidateClassification,
|
|
@@ -87,6 +91,27 @@ async function prepareInitialFreeze(run, journeyId, call, view) {
|
|
|
87
91
|
} catch (error) {
|
|
88
92
|
// A large first freeze can outlast one helper command; captured roots persist for the rerun.
|
|
89
93
|
if (error?.code === 'CLI_UNAVAILABLE') return { stage: 'freeze', status: 'running', startedAt }
|
|
94
|
+
// Every session is already on the profile or left out (ADR 0276): there is no Send.
|
|
95
|
+
if (error?.code === 'ACCOUNT_NOTHING_TO_SEND') {
|
|
96
|
+
const stopped = await call('journey.get', { journeyId })
|
|
97
|
+
return {
|
|
98
|
+
stage: 'freeze',
|
|
99
|
+
status: 'nothing-to-send',
|
|
100
|
+
startedAt,
|
|
101
|
+
...accountFormationField(stopped),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (['ACCOUNT_WORKS_UNAVAILABLE', 'ACCOUNT_SIGNED_OUT'].includes(error?.code)) {
|
|
105
|
+
const stopped = await call('journey.get', { journeyId })
|
|
106
|
+
if (stopped.accountReadRecovery)
|
|
107
|
+
return {
|
|
108
|
+
stage: 'freeze',
|
|
109
|
+
status: 'blocked',
|
|
110
|
+
startedAt,
|
|
111
|
+
accountReadRecovery: stopped.accountReadRecovery,
|
|
112
|
+
next: 'journey.prepare',
|
|
113
|
+
}
|
|
114
|
+
}
|
|
90
115
|
throw error
|
|
91
116
|
}
|
|
92
117
|
const completed = await call('journey.get', { journeyId })
|
|
@@ -194,12 +219,16 @@ async function awaitDecision(run, journeyId, waitMs, call, sleep, ensureHost) {
|
|
|
194
219
|
command: 'await',
|
|
195
220
|
from,
|
|
196
221
|
phase,
|
|
222
|
+
...(view.accountReadRecovery
|
|
223
|
+
? { accountReadRecovery: view.accountReadRecovery, next: 'journey.prepare' }
|
|
224
|
+
: {}),
|
|
197
225
|
changed,
|
|
198
226
|
waitedMs: Date.now() - started,
|
|
199
227
|
revision: view.revision,
|
|
200
228
|
blockedWorks: blocked,
|
|
201
229
|
...(held.length ? { interruptedReviews: held } : {}),
|
|
202
230
|
...leftOutField(view),
|
|
231
|
+
...accountFormationField(view),
|
|
203
232
|
...(retried.length ? { retriedWorks: retried, next: 'journey.prepare' } : {}),
|
|
204
233
|
...(handoff ? { handoff: { panel: await ensureHost(), next: 'end-turn' } } : {}),
|
|
205
234
|
}
|
|
@@ -212,7 +241,7 @@ async function runPhaseCore(
|
|
|
212
241
|
{ run, journeyId, command, stage, refresh = false, limit = 3, waitMs = 0, sleep, ensureHost },
|
|
213
242
|
call,
|
|
214
243
|
) {
|
|
215
|
-
if (!Number.isSafeInteger(limit) || limit < 1 || limit >
|
|
244
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 48) throw new Error('INVALID_ARGUMENTS')
|
|
216
245
|
// Append-batch consolidation uses immutable local drafts. No current journey view is
|
|
217
246
|
// needed until publication; retain write recovery and live value settlement on that path.
|
|
218
247
|
if (stage === 'cards' && !refresh && ['consolidate', 'ingest'].includes(command)) {
|
|
@@ -264,12 +293,16 @@ async function runPhaseCore(
|
|
|
264
293
|
? { classification: await classificationProgress(manifest) }
|
|
265
294
|
: {}),
|
|
266
295
|
phase: await currentPhase(run, view),
|
|
296
|
+
...(view.accountReadRecovery
|
|
297
|
+
? { accountReadRecovery: view.accountReadRecovery, next: 'journey.prepare' }
|
|
298
|
+
: {}),
|
|
267
299
|
revision: view.revision,
|
|
268
300
|
readingComplete: view.readingComplete,
|
|
269
301
|
pending: activeTerms(view).filter((term) => term.disposition === 'pending').length,
|
|
270
302
|
blockedWorks: blockedWorks(view),
|
|
271
303
|
...(interruptions.length ? { interruptedReviews: interruptions } : {}),
|
|
272
304
|
...leftOutField(view),
|
|
305
|
+
...accountFormationField(view),
|
|
273
306
|
answersRevision: view.privacy.answersRevision,
|
|
274
307
|
...packagingProgress(view),
|
|
275
308
|
coldElapsedMs,
|
|
@@ -575,11 +608,14 @@ async function runPhaseCore(
|
|
|
575
608
|
|
|
576
609
|
/**
|
|
577
610
|
* The complete proposal, checked before commit: after scope confirmation an item never joins
|
|
578
|
-
* placed Works with different visibility (ADR 0274),
|
|
611
|
+
* placed Works with different visibility (ADR 0274), and account Work joins follow ADR 0276
|
|
612
|
+
* slice H; CLI would refuse either.
|
|
579
613
|
*/
|
|
580
614
|
async function classificationItems(manifest, view) {
|
|
581
615
|
const items = await classificationSubmission(manifest, view)
|
|
582
|
-
|
|
616
|
+
const confirmed = view.scope?.status === 'confirmed'
|
|
617
|
+
validateAccountWorkJoins(items, view.works, confirmed)
|
|
618
|
+
if (confirmed) validateLatePlacedWorks(items, view.works)
|
|
583
619
|
return items
|
|
584
620
|
}
|
|
585
621
|
|
|
@@ -626,6 +662,7 @@ async function ingestKnownWorks(directory, manifest, view, call, partial) {
|
|
|
626
662
|
const terminalClassificationErrors = new Set([
|
|
627
663
|
'INVALID_SELECTION',
|
|
628
664
|
'CLASSIFICATION_LATE_SEVERAL_WORKS',
|
|
665
|
+
'CLASSIFICATION_ACCOUNT_WORK_JOIN',
|
|
629
666
|
'CLASSIFICATION_PROJECT_NAME_CONFLICT',
|
|
630
667
|
'CLASSIFICATION_ROOT_COVERAGE_INVALID',
|
|
631
668
|
'CLASSIFICATION_THEME_TOO_SMALL',
|
|
@@ -705,15 +742,16 @@ export async function main(argv) {
|
|
|
705
742
|
}
|
|
706
743
|
const safeId = /^[A-Za-z0-9._:-]{1,128}$/u
|
|
707
744
|
/**
|
|
708
|
-
* Stderr for a failed command: the code, and for CLASSIFICATION_LATE_SEVERAL_WORKS
|
|
709
|
-
* refused item with its index and the
|
|
745
|
+
* Stderr for a failed command: the code, and for CLASSIFICATION_LATE_SEVERAL_WORKS and
|
|
746
|
+
* CLASSIFICATION_ACCOUNT_WORK_JOIN one line per refused item with its index and the Work ids,
|
|
747
|
+
* never a title or path (ADR 0274, ADR 0276 slice H).
|
|
710
748
|
*/
|
|
711
749
|
export function failureOutput(error) {
|
|
712
750
|
const message = error?.message ?? ''
|
|
713
751
|
if (error?.code?.startsWith('ERR_PARSE_ARGS')) return 'INVALID_ARGUMENTS\n'
|
|
714
752
|
const code = /^[A-Z_]{3,80}$/u.test(message) ? message : 'JOURNEY_HELPER_FAILED'
|
|
715
753
|
const lines = [code]
|
|
716
|
-
if (code === 'CLASSIFICATION_LATE_SEVERAL_WORKS')
|
|
754
|
+
if (code === 'CLASSIFICATION_LATE_SEVERAL_WORKS' || code === 'CLASSIFICATION_ACCOUNT_WORK_JOIN')
|
|
717
755
|
for (const conflict of error.conflicts ?? [])
|
|
718
756
|
if (Number.isInteger(conflict.item))
|
|
719
757
|
lines.push(
|