@stravica-ai/digital-operator-pa 0.1.51 → 0.1.53
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/engineering-pa-cli/src/pa/work-cli.mjs +5 -0
- package/dist/engineering-pa-cli/src/pa/work.mjs +12 -2
- package/dist/engineering-pa-cli/src/scheduler/cli.mjs +15 -6
- package/dist/engineering-pa-cli/src/secrets/commands/init.mjs +35 -1
- package/dist/engineering-pa-core/docs/commands/onboard.md +33 -9
- package/dist/engineering-pa-core/roles/agentic-workflow-designer/definition.md +53 -6
- package/dist/engineering-pa-core/src/scheduler/install.mjs +74 -26
- package/dist/engineering-pa-core/src/scheduler/list.mjs +53 -28
- package/dist/engineering-pa-core/src/scheduler/remove.mjs +153 -27
- package/dist/engineering-pa-core/src/scheduler/resolve-agent.mjs +56 -0
- package/dist/engineering-pa-core/src/scheduler/verify.mjs +2 -1
- package/dist/engineering-pa-scaffold/src/generator.mjs +52 -6
- package/dist/engineering-pa-scaffold/templates/CLAUDE.md.template +15 -0
- package/dist/engineering-pa-workspace/app/nav.json +6 -0
- package/dist/engineering-pa-workspace/app/review-annotator.js +1390 -0
- package/dist/engineering-pa-workspace/app/reviews.html +534 -0
- package/dist/engineering-pa-workspace/src/api.mjs +6 -1
- package/dist/engineering-pa-workspace/src/review/api-handler.mjs +836 -0
- package/dist/engineering-pa-workspace/src/review/page-key.mjs +92 -0
- package/dist/engineering-pa-workspace/src/server.mjs +34 -22
- package/dist/engineering-pa-workspace/src/ws.mjs +47 -0
- package/docs/onboarding-solo.md +34 -7
- package/package.json +1 -1
|
@@ -36,6 +36,11 @@ Usage:
|
|
|
36
36
|
|
|
37
37
|
Notes:
|
|
38
38
|
- status=done stamps \`completedAt\` to today (only if not already set).
|
|
39
|
+
- \`list\` defaults to the active-only scope (excludes done + archived).
|
|
40
|
+
An explicit \`--status <s>\` REPLACES that default and returns only
|
|
41
|
+
items with that status (so \`--status done\` prints done items).
|
|
42
|
+
\`--all\` means no status filter; \`--mine\`/\`--theirs\` compose with
|
|
43
|
+
either. \`--status\` wins over \`--all\` when both are given.
|
|
39
44
|
- --mine resolves the PA name from operator/config/preferences.json
|
|
40
45
|
(paName / agentName). Default: "pa".
|
|
41
46
|
- --author sets the writer-scoped id's author token. Precedence:
|
|
@@ -228,6 +228,15 @@ export async function setWorkStatus(id, status) {
|
|
|
228
228
|
* mine, theirs, all, paName (used for --mine resolution).
|
|
229
229
|
* By default excludes done + archived (mirrors desk-list semantics).
|
|
230
230
|
*
|
|
231
|
+
* An explicit `status` filter REPLACES the active-only default rather
|
|
232
|
+
* than being AND-ed with it: `--status done` returns only done items,
|
|
233
|
+
* `--status archived` returns only archived items, `--status in-progress`
|
|
234
|
+
* is unchanged (the AND was harmless for non-terminal statuses). The
|
|
235
|
+
* pre-0.1.52 AND made any terminal-status request unconditionally
|
|
236
|
+
* empty ("no work items match" for `--status done`), a confident false
|
|
237
|
+
* negative. `--all` still means "no status filter at all"; if both
|
|
238
|
+
* `--all` and `--status` are given, `--status` wins.
|
|
239
|
+
*
|
|
231
240
|
* Reads any well-formed work item (both id forms). Skips files whose
|
|
232
241
|
* filename does not parse as a work-item id at all — the same
|
|
233
242
|
* skip-unparseable posture the original had, restated against the
|
|
@@ -248,10 +257,11 @@ export async function listWork(filters = {}) {
|
|
|
248
257
|
}
|
|
249
258
|
}
|
|
250
259
|
let filtered = items;
|
|
251
|
-
if (
|
|
260
|
+
if (filters.status) {
|
|
261
|
+
filtered = filtered.filter((w) => w.status === filters.status);
|
|
262
|
+
} else if (!filters.all) {
|
|
252
263
|
filtered = filtered.filter((w) => !['done', 'archived'].includes(w.status));
|
|
253
264
|
}
|
|
254
|
-
if (filters.status) filtered = filtered.filter((w) => w.status === filters.status);
|
|
255
265
|
if (filters.category) filtered = filtered.filter((w) => w.category === filters.category);
|
|
256
266
|
if (filters.project) filtered = filtered.filter((w) => w.projectId === filters.project);
|
|
257
267
|
if (filters.owner) filtered = filtered.filter((w) => w.owner === filters.owner);
|
|
@@ -25,18 +25,27 @@ Usage:
|
|
|
25
25
|
Install a schedule onto its declared backend
|
|
26
26
|
scheduler list [--backend <name>] [--agent <name>]
|
|
27
27
|
List installed schedules
|
|
28
|
-
scheduler remove <id
|
|
29
|
-
Remove an installed schedule
|
|
28
|
+
scheduler remove <id|com.<agent>.<id>> [--backend N] [--agent <name>] [--delete-file]
|
|
29
|
+
Remove an installed schedule. --backend is
|
|
30
|
+
optional: when omitted, the id is looked up in
|
|
31
|
+
schedules/<id>.json and the file's own backend
|
|
32
|
+
field wins (OS-native detect is the fallback).
|
|
33
|
+
The label form the list verb prints as a
|
|
34
|
+
secondary line is accepted here directly.
|
|
30
35
|
scheduler verify <schedule.json> Verify a schedule fires correctly
|
|
31
36
|
scheduler -h, --help Show this help
|
|
32
37
|
|
|
33
38
|
Schedule files live at <repoRoot>/schedules/<id>.json. The canonical
|
|
34
39
|
schema is bundled at @stravica-ai/engineering-pa-core/schema/schedule.json.
|
|
35
40
|
|
|
36
|
-
--agent
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
--agent targets a specific agent's label / tag prefix. Install reads
|
|
42
|
+
the agent from the schedule file's own top-level "agent" field. Since
|
|
43
|
+
0.1.52 (w-2026-09-19-dave-001), list/remove resolve the agent from the
|
|
44
|
+
invoking instance's "paName" in operator/config/preferences.json when
|
|
45
|
+
--agent is omitted, and REFUSE (exit 2) when neither source produces a
|
|
46
|
+
value — there is no hard-coded "dave" fallback. This is the fix for the
|
|
47
|
+
colleague-laptop bug where the tools would list and touch another
|
|
48
|
+
estate's schedules under a silent default.
|
|
40
49
|
|
|
41
50
|
Host enforcement (install): a schedule's "host" field is matched against
|
|
42
51
|
this machine's hostname (exact, then short-name-before-first-dot). A
|
|
@@ -85,7 +85,41 @@ function parseStdinJson(raw) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export async function runInit(overrides = {}) {
|
|
88
|
-
|
|
88
|
+
// 0.1.52 fix (w-2026-09-19-dave-001): `secrets init` CREATES an age
|
|
89
|
+
// key. The shared resolver in core/vault/require.mjs honours
|
|
90
|
+
// SOPS_AGE_KEY_FILE first, so with the variable exported the previous
|
|
91
|
+
// init generated the new key AT the env path — a stray key landed
|
|
92
|
+
// outside the instance, and on a colleague laptop that carried
|
|
93
|
+
// another estate's SOPS_AGE_KEY_FILE it would have written the new
|
|
94
|
+
// key into that estate's key file. The env override is a READ-path
|
|
95
|
+
// escape hatch; init must generate at the instance path (solo mode)
|
|
96
|
+
// or the per-user path (HQ mode) regardless of the variable.
|
|
97
|
+
//
|
|
98
|
+
// Strip SOPS_AGE_KEY_FILE from the env passed to `defaultPaths` only
|
|
99
|
+
// — every OTHER env-derived resolution (DAVE_ROOT / PA_ROOT walk,
|
|
100
|
+
// etc.) still uses the caller's real env. Print a one-line notice
|
|
101
|
+
// when the operator had the override set and the resolved
|
|
102
|
+
// key path does not match it, so subsequent reads that continue to
|
|
103
|
+
// consult the env override are not silently pointed at the old file.
|
|
104
|
+
const rawEnv = overrides.env;
|
|
105
|
+
const envOverride = rawEnv && typeof rawEnv.SOPS_AGE_KEY_FILE === 'string' && rawEnv.SOPS_AGE_KEY_FILE.length > 0
|
|
106
|
+
? rawEnv.SOPS_AGE_KEY_FILE
|
|
107
|
+
: null;
|
|
108
|
+
let envForPaths = rawEnv;
|
|
109
|
+
if (envOverride !== null && !overrides.paths) {
|
|
110
|
+
envForPaths = { ...rawEnv };
|
|
111
|
+
delete envForPaths.SOPS_AGE_KEY_FILE;
|
|
112
|
+
}
|
|
113
|
+
const paths = overrides.paths || defaultPaths({
|
|
114
|
+
...overrides,
|
|
115
|
+
env: envForPaths,
|
|
116
|
+
allowGitRootWithoutVault: true,
|
|
117
|
+
});
|
|
118
|
+
if (envOverride !== null && envOverride !== paths.keyFile) {
|
|
119
|
+
console.log(
|
|
120
|
+
`Note: SOPS_AGE_KEY_FILE is set to ${envOverride}; init writes the new key to ${paths.keyFile}. Reads will not see the new key until the variable is unset or repointed.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
89
123
|
const argv = overrides.argv || [];
|
|
90
124
|
const flags = extractInitFlags(argv);
|
|
91
125
|
|
|
@@ -77,16 +77,25 @@ done and move on. On a mismatch, print both versions and the recovery line
|
|
|
77
77
|
|
|
78
78
|
### 3. profileIntake
|
|
79
79
|
|
|
80
|
-
Ask the operator four questions in one turn
|
|
80
|
+
Ask the operator four questions in one turn. Question 4 is optional
|
|
81
|
+
and explicitly skippable — a first-day operator often has no strong
|
|
82
|
+
answer and a forced answer here becomes a stale steering signal for
|
|
83
|
+
the whole life of the instance. "Skip" is the safe default when the
|
|
84
|
+
operator hesitates.
|
|
81
85
|
|
|
82
86
|
1. Your name and how you prefer to be addressed?
|
|
83
87
|
2. Your role (job title, or "solo", or a short description)?
|
|
84
88
|
3. Communication style you want (formal, casual, direct, verbose)?
|
|
85
|
-
4.
|
|
89
|
+
4. (Optional, skip freely) Anything you would like the PA to lean into
|
|
90
|
+
right away? A one-line hint about the kind of work you plan to
|
|
91
|
+
throw at it (writing, code, admin, research, planning). This tunes
|
|
92
|
+
nothing critical; the PA discovers your patterns as it goes.
|
|
86
93
|
|
|
87
|
-
Take the answers and write them into `operator/profile/profile.md`,
|
|
88
|
-
the placeholder. Keep it a short human-readable paragraph
|
|
89
|
-
of preferences.
|
|
94
|
+
Take the answers and write them into `operator/profile/profile.md`,
|
|
95
|
+
overwriting the placeholder. Keep it a short human-readable paragraph
|
|
96
|
+
plus a bulleted list of preferences. If Q4 was skipped or the answer
|
|
97
|
+
is `skip` / empty, do not write a Q4 line at all. Do not invent fields
|
|
98
|
+
the operator did not name.
|
|
90
99
|
|
|
91
100
|
### 4. aliasOffer
|
|
92
101
|
|
|
@@ -189,6 +198,19 @@ and the operator gets:
|
|
|
189
198
|
Then stop. Do not silently skip to `atlassianOffer`. The whole
|
|
190
199
|
onboarding is blocked on this step.
|
|
191
200
|
|
|
201
|
+
**Hard stop between step 6 and step 7.** Do not deliver the step-7
|
|
202
|
+
vault pitch, do not describe the vault, do not ask the operator for
|
|
203
|
+
credential values, and do not print the `secrets init` invocation while
|
|
204
|
+
`librarianSchedule` is anything other than `done` in the state file.
|
|
205
|
+
Step 7 is only reached AFTER `librarianSchedule` transitions to `done`
|
|
206
|
+
and a completed run is recorded in `.cache/distill/logs/runs.jsonl`.
|
|
207
|
+
If the operator asks about the vault in the meantime, name the pending
|
|
208
|
+
check (`librarianSchedule` awaiting a proven run) and pick step 7 back
|
|
209
|
+
up once the librarian step lands. A pre-emptive step-7 pitch reads to
|
|
210
|
+
the operator as a step already begun, and the vault step's first-time
|
|
211
|
+
material is expensive to redo cleanly if it lands under the wrong
|
|
212
|
+
prerequisite.
|
|
213
|
+
|
|
192
214
|
### 7. vault (conditionally mandatory, no opt-out)
|
|
193
215
|
|
|
194
216
|
Stand up the SOPS+age secrets vault when an enabled tool declares a
|
|
@@ -207,10 +229,12 @@ Otherwise, tell the operator what is happening in one short paragraph:
|
|
|
207
229
|
|
|
208
230
|
> Standing up your instance's own SOPS+age vault. This is what
|
|
209
231
|
> credentialed tools read their keys from. The vault lives in
|
|
210
|
-
> `.vault/`, the age private key lives
|
|
211
|
-
>
|
|
212
|
-
>
|
|
213
|
-
>
|
|
232
|
+
> `.vault/`, the age private key lives inside the instance at
|
|
233
|
+
> `.vault/age-key.txt` (never committed), and the encrypted store is
|
|
234
|
+
> safe to commit. Values you enter here never leave your laptop.
|
|
235
|
+
> HQ-mode instances keep a per-user key at
|
|
236
|
+
> `~/.config/stravica-pa/age-key.txt` instead (shared across the
|
|
237
|
+
> estate's canonical clones).
|
|
214
238
|
|
|
215
239
|
Ask, in one turn, for the values the enabled tools declare. For the
|
|
216
240
|
default atlassian install:
|
|
@@ -16,7 +16,7 @@ The role exists because workflow design keeps recurring as ad-hoc work done inli
|
|
|
16
16
|
| Reports to | The dispatching PA |
|
|
17
17
|
| Orchestrator mode | No -- one designer produces one ruled workflow design end to end |
|
|
18
18
|
| Primary output | Versioned flowchart diagram + Rule of Two mapping + open-questions/rulings record, registered as a project doc |
|
|
19
|
-
| Activation |
|
|
19
|
+
| Activation | Routing cues surfaced at the PA layer (see Activation); dispatched with the 8-section briefing contract |
|
|
20
20
|
| Domain | Agentic pipelines, scheduled automation, triage and approval flows, cross-estate processes |
|
|
21
21
|
|
|
22
22
|
|
|
@@ -43,6 +43,32 @@ The mapping shape, one row per autonomous loop:
|
|
|
43
43
|
|---|---|---|---|---|
|
|
44
44
|
| `<loop name>` | yes / no / constrained: `<control>` | yes / no / constrained: `<control>` | yes / no / constrained: `<control>` | `<what the operator approves before the next lane runs>` |
|
|
45
45
|
|
|
46
|
+
**This mapping is not the autonomy envelope, and the two do not translate.** Estates already carry a per-task-type approval vocabulary (`pa autonomy`: `fullyApproved` / `checkBeforeCommitting` / `needsFullSupervision`), and under pressure that vocabulary wins by default because the platform supplied it first. They answer different questions. The autonomy envelope classifies task types by how much the operator trusts the agent to act unsupervised; the Rule of Two mapping classifies a loop's capabilities by what attacker-influenced content could do with them. An envelope state cannot express which of [A]/[B]/[C] a loop holds, so it cannot show whether the rule is satisfied; a mapping cannot express whether the operator wants sign-off on a task type. There is no crosswalk: `fullyApproved` is not `yes`, `needsFullSupervision` is not a gate, and a mapping delivered in envelope vocabulary is wrong in kind, not in wording -- it is redone in the table shape above, which is the only valid form of the mapping; prose restatements in any vocabulary are commentary, not the mapping. Where the two systems genuinely touch, state the relationship as a consequence, never a synonym: a loop whose mapping requires an operator gate will normally sit no higher than `checkBeforeCommitting` in the estate's envelope, and the diagram may note that beside the gate, clearly labelled as the envelope's word.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Required estate bindings
|
|
50
|
+
|
|
51
|
+
The role depends on two estate surfaces. Both are declared here so absence is a loud, checkable condition and never a silent change of deliverable. One is a hard dependency; the other splits into a half that ships in core and a half that is estate-optional with a stock-safe default.
|
|
52
|
+
|
|
53
|
+
**1. Graphic-design rendering lane -- hard dependency, checked before design work starts.** The brief must name the lane binding: engine, invocation form, and model pin. The designer's first act on activation is a binding check: confirm the named lane exists and answers (a version or no-op invocation is sufficient). If the brief names no lane, or the named lane does not answer, the designer returns immediately with a single line -- `BINDING-MISSING: graphic-design lane -- <what the brief named, what was checked, what was absent>` -- and produces no artefact. This is the only permitted return that replaces the six-line contract. Absence of the lane never changes the artefact type: no markdown diagram, no mermaid block, no knowledge-page substitute, no self-authored HTML on the orchestrating model. A wrong-shaped artefact reads as a delivered design and buries the estate defect; a loud stop gets the binding registered once, for every future dispatch. An unregistered binary on the machine (a `codex` on the PATH, say) is not a binding; a binding is named in the brief or in the estate's declared overlay, nowhere else.
|
|
54
|
+
|
|
55
|
+
**2. Review loop -- split, and the split is precise.** The doc-status half ships in core and works on a stock instance: `pa project doc add` registers the diagram, `pa project doc status <draft|review|approved>` tracks the loop. The role uses these unconditionally and may assume them. The comment-landing half -- a surface where the operator's rulings physically arrive against the artefact (a reviews inbox, an annotator) -- is not core. It is an estate extension and is never assumed. The ruling-capture channel is an overlay point with a stock-safe default:
|
|
56
|
+
|
|
57
|
+
| Overlay point | Core default | Estate example |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| Ruling-capture channel | Rulings arrive through the dispatching PA: the operator rules in conversation, the PA carries the numbered rulings into the revision dispatch, and they are staged verbatim per the operator review loop | An estate-bound reviews inbox and annotator on the workspace |
|
|
60
|
+
|
|
61
|
+
On a stock instance the full loop is: doc registered at `draft`, moved to `review` when the v1 URL goes to the operator, operator reads at the serving URL and rules through the PA, ruled revision ships at the same URL, doc moved to `approved`. Nothing in this role's process may require a surface the estate has not declared; where this definition and an estate's actual surfaces disagree, the designer treats the estate as authoritative and flags the gap in `HANDBACK:` rather than designing against the missing surface.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## Design verification (the plan, not the page)
|
|
65
|
+
|
|
66
|
+
Render verification proves the diagram displays; it says nothing about whether the design is true. Before handover the designer verifies the plan itself, and states the outcome -- verification that happened but was not stated is treated as not having happened.
|
|
67
|
+
|
|
68
|
+
- **Grounding.** Every external fact the design leans on -- system states and status sets, permissions, endpoints, schedule mechanics, tool availability -- is verified against the live system where the estate's access allows, or explicitly marked `assumed` and carried as a numbered open question. An unverifiable fact never silently becomes a design premise.
|
|
69
|
+
- **Coverage.** Every autonomous loop drawn on the diagram has a mapping row and every mapping row corresponds to a drawn loop; every gate in the mapping is drawn and every drawn gate appears in the mapping. A diagram/mapping mismatch is a design defect, not a formatting nit.
|
|
70
|
+
- **Statement.** The plan summary carries one explicit verification line: what was checked, how, and what remains assumed.
|
|
71
|
+
|
|
46
72
|
|
|
47
73
|
## The diagram contract
|
|
48
74
|
|
|
@@ -85,7 +111,7 @@ Standing rules for the lane invocation, whatever engine the estate binds:
|
|
|
85
111
|
## Process obligations
|
|
86
112
|
|
|
87
113
|
- A work item exists before design work starts; dispatches are logged per the estate's spawn discipline.
|
|
88
|
-
- The design lands in a project
|
|
114
|
+
- **The design lands in a project, and the project bootstrap is owned, not assumed.** The brief names an existing `projectId` when the design belongs to work already underway. When it does not, the designer creates the project itself -- as its first act after the binding check -- via the estate's project tooling (`pa project create`, slug named for the workflow), and reports the created id on the `REGISTRATION:` line. The dispatching PA is never required to pre-create a project in order to dispatch this role: bootstrap cost on the dispatch path is routing friction, and routing friction is what sent the first proving run into the knowledge tree, which needed no bootstrap. The diagram is registered as a project doc immediately on first write (`pa project doc add`, status `draft`), not at handover.
|
|
89
115
|
- Rulings and design-shaping decisions are staged to the knowledge pipeline in real time, not batched.
|
|
90
116
|
|
|
91
117
|
|
|
@@ -96,6 +122,7 @@ The definition is PA-agnostic and operator-agnostic. These bindings are supplied
|
|
|
96
122
|
| Overlay point | Core default | Estate example |
|
|
97
123
|
|---|---|---|
|
|
98
124
|
| Graphic-design lane | The estate's designated engine, invoked as a CLI with the model pinned on the command line | HQ and WSD: Codex `gpt-6-astra` one-shot via `codex exec` (the judgement, content and design lane under the 2026-09-13 allocation) |
|
|
125
|
+
| Ruling-capture channel | Rulings arrive through the dispatching PA: the operator rules in conversation, the PA carries the numbered rulings into the revision dispatch, and they are staged verbatim per the operator review loop | An estate-bound reviews inbox and annotator on the workspace |
|
|
99
126
|
| Editorial hygiene | Operator-profile-driven | No emojis, no em-dashes in persisted artefacts |
|
|
100
127
|
| Diagram theme | Operator display preference; light when unstated | Hard-coded light theme |
|
|
101
128
|
| Serving surface | The instance workspace at the configured `workspacePort` (`viewer.html?file=<path>` or direct path serving) | WSD workspace, repo-path serving on port 4222 |
|
|
@@ -121,7 +148,8 @@ Every designer dispatch returns:
|
|
|
121
148
|
- `DIAGRAM:` file path, serving URL, version marker text
|
|
122
149
|
- `MAPPING:` the [A]/[B]/[C] table per autonomous loop in the duty-1 vocabulary, with the gate placements
|
|
123
150
|
- `QUESTIONS/RULINGS:` open questions (v1) or the rulings folded in (vN), with anything still provisional flagged
|
|
124
|
-
- `VERIFICATION:`
|
|
151
|
+
- `VERIFICATION (design):` what was verified against live systems and how, the diagram/mapping coverage check, and what remains assumed (per Design verification)
|
|
152
|
+
- `VERIFICATION (render):` render evidence (console state, external-request count, elements confirmed)
|
|
125
153
|
- `REGISTRATION:` project doc path and status
|
|
126
154
|
- `HANDBACK:` what the parent does next -- rulings to obtain, spec to ship cross-estate, build items to open
|
|
127
155
|
|
|
@@ -132,7 +160,7 @@ The parent only sees the final message. The return contract is the deliverable.
|
|
|
132
160
|
|
|
133
161
|
- **Designs; never implements the workflow in the same dispatch.** Build work is separate, briefed against the ruled design.
|
|
134
162
|
- **Never weakens or removes a Rule of Two gate** on its own judgement; that is an operator ruling.
|
|
135
|
-
- **No mutation of the systems the workflow will touch.** The design artefact,
|
|
163
|
+
- **No mutation of the systems the workflow will touch.** The design artefact, the project that carries it (creation and doc registration via the estate's project tooling, when the brief names no existing project), and the staged rulings are the only writes.
|
|
136
164
|
- **Depth-2 posture: the designer spawns no agents.** The graphic-design lane is a tool call from the designer's own session; an agent-role lane is a sibling dispatch run by the PA.
|
|
137
165
|
|
|
138
166
|
|
|
@@ -164,10 +192,29 @@ Stop and escalate if:
|
|
|
164
192
|
|
|
165
193
|
- The workflow cannot be decomposed to satisfy the Rule of Two without destroying its purpose; the operator decides the risk posture.
|
|
166
194
|
- An operator instruction appears to conflict with the Rule of Two; surface the conflict with the mapping, do not silently comply or silently refuse.
|
|
167
|
-
- The graphic-design lane is unavailable or fails within the bounded iterations.
|
|
195
|
+
- The graphic-design lane is unavailable at the activation binding check, or fails within the bounded iterations.
|
|
168
196
|
- The brief lacks the actors, external stores, or trust boundaries needed to produce the [A]/[B]/[C] mapping; do not guess a security architecture.
|
|
169
197
|
|
|
170
198
|
|
|
171
199
|
## Activation
|
|
172
200
|
|
|
173
|
-
|
|
201
|
+
### Routing cues (for the dispatching PA -- lift this block verbatim)
|
|
202
|
+
|
|
203
|
+
This block is written in the dispatcher's vocabulary, not the designer's, and it is designed to be composed into the PA's own operating surface by the platform. A cue that lives only in a role file the PA never opens routes nothing; the role declares the cues here, and the estate is responsible for surfacing them where routing decisions are made. The recognition test is deliberately one question, because a test that costs more thought than answering inline will lose to answering inline every time.
|
|
204
|
+
|
|
205
|
+
**The test: would a correct answer contain a flow -- more than one actor, or at least one decision or approval point?** If yes, dispatch this role. The PA does not sketch the flow inline first; an inline sketch is the deliverable done badly, and it anchors the operator on an unruled, unmapped design.
|
|
206
|
+
|
|
207
|
+
Operator phrasings that match, however small the ask sounds:
|
|
208
|
+
|
|
209
|
+
- "design a process / workflow / pipeline / automation" for anything
|
|
210
|
+
- anything involving status transitions, approval steps, handoffs, triage rules, escalation paths, or "who does what when"
|
|
211
|
+
- any automation that will run unattended: scheduled, triggered, or looping
|
|
212
|
+
- a correction of a prose answer toward an artefact ("as a designed workflow", "produce the real artefact", "not prose") -- a second ask in this shape after an inline answer means the first ask was a missed dispatch, not that a new kind of request has arrived
|
|
213
|
+
|
|
214
|
+
Non-cues (answer inline): a status question about an existing workflow; a factual question about this role or the Rule of Two; a prose edit to a ruled diagram's supporting text that changes no lane, gate, or mapping.
|
|
215
|
+
|
|
216
|
+
**A workflow design is project work, never knowledge capture.** Writing the design into the knowledge tree (`knowledge/`, `operator/knowledge/`) is a routing failure even when the content is good: knowledge records what has been ruled; the project carries what is being designed and reviewed. The knowledge tree is also the cheaper path on a fresh instance precisely because it needs no bootstrap, which is why this rule is stated here and not left to taste.
|
|
217
|
+
|
|
218
|
+
### Activation brief (what arrives)
|
|
219
|
+
|
|
220
|
+
The Agentic Workflow Designer activates when the PA delegates a workflow design with a briefing contract. The brief names the workflow's purpose, actors, external stores, trust boundaries, cadence constraints, the project id where one already exists (see Process obligations for bootstrap when none does), the output paths, the estate's lane binding and model pin, and the designer's own model pin, and it carries the standard return-timing clause (return on completion or abort only). No external dependencies beyond the estate's lane tooling, a headless browser for render verification, and the project tooling for registration and bootstrap.
|
|
@@ -25,30 +25,58 @@ import { findRepoRootSync } from '../instance-root/find.mjs';
|
|
|
25
25
|
const __filename = fileURLToPath(import.meta.url);
|
|
26
26
|
const __dirname = dirname(__filename);
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Return the schedule's `agent` field, refusing when it is missing.
|
|
30
|
+
* 0.1.53 item E: the scheduler no longer carries a literal-`dave`
|
|
31
|
+
* default. Every schedule file has always declared its own `agent`
|
|
32
|
+
* field (the scaffold's `buildDistillationScheduleJson` sets it, the
|
|
33
|
+
* shared JSON schema documents it, and 0.1.52's `list`/`remove` do
|
|
34
|
+
* not silently default it either). A file without an `agent` field
|
|
35
|
+
* is a schema break, and refusing here is safer than silently
|
|
36
|
+
* spraying `dave-<id>` tags across whatever estate this laptop
|
|
37
|
+
* carries.
|
|
38
|
+
* @param {object} sched
|
|
39
|
+
* @returns {string}
|
|
40
|
+
* @throws {Error & { exitCode: 2 }}
|
|
41
|
+
*/
|
|
42
|
+
export function schedAgent(sched) {
|
|
43
|
+
if (sched && typeof sched.agent === 'string' && sched.agent.length > 0) {
|
|
44
|
+
return sched.agent;
|
|
45
|
+
}
|
|
46
|
+
const err = new Error(
|
|
47
|
+
`scheduler: schedule file is missing the top-level "agent" field. Add `
|
|
48
|
+
+ '"agent": "<paName>" alongside "id" (matches operator/config/preferences.json:paName). '
|
|
49
|
+
+ 'The pre-0.1.53 default `"dave"` has been removed to keep cross-estate laptops safe.',
|
|
50
|
+
);
|
|
51
|
+
err.exitCode = 2;
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
30
54
|
|
|
31
55
|
/**
|
|
32
56
|
* Compute the launchd label prefix for an agent. Centralised so install,
|
|
33
|
-
* remove, and list all agree on the naming scheme.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
57
|
+
* remove, and list all agree on the naming scheme. The agent name comes
|
|
58
|
+
* from the schedule file (install path) or the invoking instance's
|
|
59
|
+
* `paName` (list/remove path); there is no hard-coded default.
|
|
60
|
+
* @param {string} agent
|
|
36
61
|
* @returns {string}
|
|
37
62
|
*/
|
|
38
63
|
export function launchdLabelPrefix(agent) {
|
|
39
|
-
|
|
64
|
+
if (!agent) {
|
|
65
|
+
const err = new Error('launchdLabelPrefix: agent is required (no default).');
|
|
66
|
+
err.exitCode = 2;
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
return `com.${agent}.`;
|
|
40
70
|
}
|
|
41
71
|
|
|
42
72
|
/**
|
|
43
73
|
* Resolve the full launchd label for a schedule. Agent-derived: reads
|
|
44
|
-
* `sched.agent`
|
|
45
|
-
* DEFAULT_AGENT so existing schedule files with no `agent` field keep
|
|
46
|
-
* producing exactly `com.dave.<id>`, unchanged from pre-feature behaviour.
|
|
74
|
+
* `sched.agent` and refuses when it is absent (see schedAgent).
|
|
47
75
|
* @param {object} sched
|
|
48
76
|
* @returns {string}
|
|
49
77
|
*/
|
|
50
78
|
export function resolveLaunchdLabel(sched) {
|
|
51
|
-
return `${launchdLabelPrefix(sched
|
|
79
|
+
return `${launchdLabelPrefix(schedAgent(sched))}${sched.id}`;
|
|
52
80
|
}
|
|
53
81
|
|
|
54
82
|
/**
|
|
@@ -81,12 +109,31 @@ function readSchedule(filePath) {
|
|
|
81
109
|
}
|
|
82
110
|
|
|
83
111
|
/**
|
|
84
|
-
* Compute the resolved
|
|
112
|
+
* Compute the resolved PA repo root for the current install. 0.1.53
|
|
113
|
+
* item E: local resolution first, PA_ROOT as an explicit override
|
|
114
|
+
* (never leading), DAVE_ROOT accepted as a deprecated alias with a
|
|
115
|
+
* one-line notice.
|
|
85
116
|
* @returns {string}
|
|
86
117
|
*/
|
|
87
118
|
function computeDaveRoot() {
|
|
88
|
-
|
|
119
|
+
const local = findRepoRootSync();
|
|
120
|
+
if (process.env.PA_ROOT) return process.env.PA_ROOT;
|
|
121
|
+
if (process.env.DAVE_ROOT) {
|
|
122
|
+
// Emit the notice at most once per process to keep install output
|
|
123
|
+
// tidy; the flag lives on the resolver so tests can reset it.
|
|
124
|
+
if (!computeDaveRoot._noticed) {
|
|
125
|
+
process.stderr.write(
|
|
126
|
+
'Notice: DAVE_ROOT is a deprecated alias for PA_ROOT. Set PA_ROOT '
|
|
127
|
+
+ 'instead; DAVE_ROOT support will be removed in a future release.\n',
|
|
128
|
+
);
|
|
129
|
+
computeDaveRoot._noticed = true;
|
|
130
|
+
}
|
|
131
|
+
return process.env.DAVE_ROOT;
|
|
132
|
+
}
|
|
133
|
+
return local;
|
|
89
134
|
}
|
|
135
|
+
// Reset hook for tests.
|
|
136
|
+
computeDaveRoot._noticed = false;
|
|
90
137
|
|
|
91
138
|
/**
|
|
92
139
|
* Resolve the DAVE_ROOT variable and working directory.
|
|
@@ -271,9 +318,10 @@ export function checkNameCollision(scheduleFile, sched, opts = {}) {
|
|
|
271
318
|
continue;
|
|
272
319
|
}
|
|
273
320
|
if (other && other.id === sched.id) {
|
|
321
|
+
const agent = schedAgent(sched);
|
|
274
322
|
const e = new Error(
|
|
275
323
|
`schedule id "${sched.id}" collides with ${candidate} — two different schedule files `
|
|
276
|
-
+ `resolve to the same task id/tag ("
|
|
324
|
+
+ `resolve to the same task id/tag ("${agent}-${sched.id}"). Rename one of the ids before installing.`,
|
|
277
325
|
);
|
|
278
326
|
e.exitCode = 2;
|
|
279
327
|
throw e;
|
|
@@ -312,13 +360,12 @@ export function detectNvm(homeDir = homedir()) {
|
|
|
312
360
|
|
|
313
361
|
/**
|
|
314
362
|
* Default per-run log path for a schedule's cron entry, matching the
|
|
315
|
-
* real house pattern (`/tmp
|
|
316
|
-
* agent=dave, id=knowledge-eval-daily).
|
|
363
|
+
* real house pattern (`/tmp/<agent>-<id>.log`).
|
|
317
364
|
* @param {object} sched
|
|
318
365
|
* @returns {string}
|
|
319
366
|
*/
|
|
320
367
|
export function defaultCronLogPath(sched) {
|
|
321
|
-
return `/tmp/${sched
|
|
368
|
+
return `/tmp/${schedAgent(sched)}-${sched.id}.log`;
|
|
322
369
|
}
|
|
323
370
|
|
|
324
371
|
/**
|
|
@@ -416,14 +463,15 @@ export function buildCronCommand(sched, workDir, env, opts = {}) {
|
|
|
416
463
|
* @returns {string}
|
|
417
464
|
*/
|
|
418
465
|
export function buildCronLine(sched, workDir, env, opts = {}) {
|
|
419
|
-
const tag =
|
|
466
|
+
const tag = `${schedAgent(sched)}-${sched.id}`;
|
|
420
467
|
const { wrapped } = buildCronCommand(sched, workDir, env, opts);
|
|
421
468
|
return `${sched.schedule} ${wrapped} # ${tag}`;
|
|
422
469
|
}
|
|
423
470
|
|
|
424
471
|
function installCron(sched, workDir) {
|
|
472
|
+
const agent = schedAgent(sched);
|
|
425
473
|
const env = resolveEnvironment(sched);
|
|
426
|
-
const tag = `#
|
|
474
|
+
const tag = `# ${agent}-${sched.id}`;
|
|
427
475
|
const hasNvm = detectNvm();
|
|
428
476
|
const entry = buildCronLine(sched, workDir, env, { hasNvm });
|
|
429
477
|
|
|
@@ -444,10 +492,10 @@ function installCron(sched, workDir) {
|
|
|
444
492
|
const newCrontab = filtered ? `${filtered}\n${entry}\n` : `${entry}\n`;
|
|
445
493
|
execSync('crontab -', { input: newCrontab, encoding: 'utf8' });
|
|
446
494
|
|
|
447
|
-
console.log(`Installed cron schedule:
|
|
495
|
+
console.log(`Installed cron schedule: ${agent}-${sched.id}`);
|
|
448
496
|
console.log(` ${sched.schedule} -- ${sched.description || ''}`);
|
|
449
497
|
|
|
450
|
-
return { tag:
|
|
498
|
+
return { tag: `${agent}-${sched.id}` };
|
|
451
499
|
}
|
|
452
500
|
|
|
453
501
|
// ── Backend: launchd ─────────────────────────────────────────────────
|
|
@@ -562,7 +610,7 @@ function validateLaunchdDaveRoot(value) {
|
|
|
562
610
|
+ '(DAVE_ROOT unset at install time, falling back to the installed '
|
|
563
611
|
+ 'package\'s own directory, which is version-pinned and silently breaks '
|
|
564
612
|
+ 'on the next upgrade or package rename). Set DAVE_ROOT explicitly in '
|
|
565
|
-
+ 'the shell running `
|
|
613
|
+
+ 'the shell running `pnpm exec scheduler install`, or set the schedule\'s '
|
|
566
614
|
+ 'environment.DAVE_ROOT to the real operator repo root.',
|
|
567
615
|
);
|
|
568
616
|
e.exitCode = 2;
|
|
@@ -696,7 +744,7 @@ export function buildStartCalendarIntervalBlock(cron) {
|
|
|
696
744
|
* Generate the full plist content for a schedule. Pure function — safe to
|
|
697
745
|
* call in tests. Always emits an EnvironmentVariables block on launchd
|
|
698
746
|
* (auto-injected defaults when the schedule has no explicit environment).
|
|
699
|
-
* The launchd Label is agent-derived (`com.<sched.agent
|
|
747
|
+
* The launchd Label is agent-derived (`com.<sched.agent>.<id>`) —
|
|
700
748
|
* see resolveLaunchdLabel.
|
|
701
749
|
* @param {object} sched
|
|
702
750
|
* @param {string} workDir
|
|
@@ -736,9 +784,9 @@ ${programArgs}
|
|
|
736
784
|
<key>WorkingDirectory</key>
|
|
737
785
|
<string>${xmlEscape(workDir)}</string>
|
|
738
786
|
${envBlock}${calendarBlock} <key>StandardOutPath</key>
|
|
739
|
-
<string>/tmp
|
|
787
|
+
<string>/tmp/${schedAgent(sched)}-${sched.id}-stdout.log</string>
|
|
740
788
|
<key>StandardErrorPath</key>
|
|
741
|
-
<string>/tmp
|
|
789
|
+
<string>/tmp/${schedAgent(sched)}-${sched.id}-stderr.log</string>
|
|
742
790
|
<key>RunAtLoad</key>
|
|
743
791
|
<false/>
|
|
744
792
|
</dict>
|
|
@@ -820,7 +868,7 @@ WantedBy=timers.target
|
|
|
820
868
|
}
|
|
821
869
|
|
|
822
870
|
function installSystemd(sched, workDir, cron) {
|
|
823
|
-
const unitName =
|
|
871
|
+
const unitName = `${schedAgent(sched)}-${sched.id}`;
|
|
824
872
|
const unitDir = join(homedir(), '.config', 'systemd', 'user');
|
|
825
873
|
mkdirSync(unitDir, { recursive: true });
|
|
826
874
|
|
|
@@ -868,7 +916,7 @@ export function buildSchtasksCommand(sched, workDir, env) {
|
|
|
868
916
|
}
|
|
869
917
|
|
|
870
918
|
function installSchtasks(sched, workDir, cron) {
|
|
871
|
-
const taskName =
|
|
919
|
+
const taskName = `${schedAgent(sched)}-${sched.id}`;
|
|
872
920
|
const env = resolveEnvironment(sched);
|
|
873
921
|
|
|
874
922
|
const fmtHour = cron.hour === '*' ? '00' : cron.hour.padStart(2, '0');
|