@stravica-ai/digital-operator-pa 0.1.49 → 0.1.51

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.
Files changed (45) hide show
  1. package/dist/engineering-pa-cli/src/pa/cli.mjs +94 -4
  2. package/dist/engineering-pa-cli/src/pa/onboard.mjs +144 -0
  3. package/dist/engineering-pa-cli/src/secrets/cli.mjs +26 -3
  4. package/dist/engineering-pa-cli/src/secrets/commands/init.mjs +128 -0
  5. package/dist/engineering-pa-cli/src/secrets/commands/set.mjs +32 -12
  6. package/dist/engineering-pa-cli/src/secrets/vault.mjs +87 -4
  7. package/dist/engineering-pa-core/README.md +1 -1
  8. package/dist/engineering-pa-core/docs/commands/onboard.md +92 -29
  9. package/dist/engineering-pa-core/docs/pa-operating-model.md +1 -0
  10. package/dist/engineering-pa-core/roles/agentic-workflow-designer/definition.md +173 -0
  11. package/dist/engineering-pa-core/src/distill/health.mjs +170 -6
  12. package/dist/engineering-pa-core/src/distill/run.mjs +19 -0
  13. package/dist/engineering-pa-core/src/instance-root/find.mjs +104 -0
  14. package/dist/engineering-pa-core/src/knowledge/retrieval/backlink-indexer.mjs +6 -1
  15. package/dist/engineering-pa-core/src/knowledge/retrieval/graph-builder.mjs +6 -1
  16. package/dist/engineering-pa-core/src/knowledge/retrieval/index-builder.mjs +4 -1
  17. package/dist/engineering-pa-core/src/knowledge/retrieval/query.mjs +6 -1
  18. package/dist/engineering-pa-core/src/scheduler/install.mjs +41 -4
  19. package/dist/engineering-pa-core/src/scheduler/list.mjs +34 -13
  20. package/dist/engineering-pa-core/src/scheduler/remove.mjs +2 -1
  21. package/dist/engineering-pa-core/src/vault/mode.mjs +50 -0
  22. package/dist/engineering-pa-core/src/vault/require.mjs +306 -0
  23. package/dist/engineering-pa-core/src/vault/tool-needs.mjs +74 -0
  24. package/dist/engineering-pa-core-extensions-atlassian/index.mjs +1 -1
  25. package/dist/engineering-pa-core-extensions-atlassian/package.json +3 -0
  26. package/dist/engineering-pa-core-extensions-atlassian/src/common/secrets.mjs +60 -0
  27. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/client.mjs +7 -1
  28. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/mirror-folder.mjs +4 -2
  29. package/dist/engineering-pa-core-extensions-atlassian/src/jira/client.mjs +7 -1
  30. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/link.mjs +4 -2
  31. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/sync-statuses.mjs +6 -2
  32. package/dist/engineering-pa-core-extensions-atlassian/src/jira/workflow-chains.mjs +7 -3
  33. package/dist/engineering-pa-core-extensions-atlassian/src/jira/workflow-map.mjs +9 -3
  34. package/dist/engineering-pa-scaffold/src/generator.mjs +13 -1
  35. package/dist/engineering-pa-workspace/app/notes.html +1 -1
  36. package/dist/engineering-pa-workspace/app/project-detail.html +17 -1
  37. package/dist/engineering-pa-workspace/app/projects.html +1 -1
  38. package/dist/engineering-pa-workspace/app/tasks.html +1 -1
  39. package/dist/engineering-pa-workspace/src/install-daemon.mjs +2 -2
  40. package/dist/engineering-pa-workspace/src/repo-root.mjs +13 -100
  41. package/dist/engineering-pa-workspace/src/server.mjs +2 -2
  42. package/dist/engineering-pa-workspace/src/ws.mjs +79 -10
  43. package/docs/onboarding-solo.md +20 -3
  44. package/docs/what-it-can-do.md +1 -0
  45. package/package.json +1 -1
@@ -16,6 +16,8 @@ import { join, dirname, resolve, basename, sep } from 'node:path';
16
16
  import { fileURLToPath } from 'node:url';
17
17
  import { mkdtempSync } from 'node:fs';
18
18
  import { tmpdir } from 'node:os';
19
+ import { isSoloMode } from '../../../engineering-pa-core/src/vault/mode.mjs';
20
+ import { resolveKeyFile as coreResolveKeyFile } from '../../../engineering-pa-core/src/vault/require.mjs';
19
21
 
20
22
  // ── Path resolution ──────────────────────────────────────────────────
21
23
 
@@ -155,8 +157,34 @@ export function defaultPaths(overrides = {}) {
155
157
  allowGitRootWithoutVault: overrides.allowGitRootWithoutVault,
156
158
  }).root;
157
159
  const home = overrides.home ?? homedir();
160
+ // Pass caller-supplied env only. The resolver treats undefined env as
161
+ // empty (no SOPS_AGE_KEY_FILE consulted) so `defaultPaths` under a
162
+ // test harness that never passed `env` is never subject to a leaked
163
+ // env-override from the developer's shell. Real CLI invocations
164
+ // thread `env: process.env` in via `main()` in cli.mjs.
165
+ const env = overrides.env;
158
166
  const vaultDir = overrides.vaultDir ?? join(root, '.vault');
159
- const keyDir = overrides.keyDir ?? join(home, '.config', 'stravica-pa');
167
+ // 0.1.51 fix round 2 (w-2026-09-18-dave-007): the key file resolves
168
+ // through the ONE shared resolver in core/vault so init, get, set,
169
+ // status, requireVault, and the atlassian resolver all agree on the
170
+ // active key path. Solo mode → in-instance `.vault/age-key.txt`;
171
+ // HQ mode → per-user `~/.config/stravica-pa/age-key.txt`; explicit
172
+ // `SOPS_AGE_KEY_FILE` env override always wins. Callers that hand-
173
+ // craft `overrides.keyFile` or `overrides.keyDir` bypass the resolver
174
+ // entirely so the existing test helpers keep their isolated key dir.
175
+ let keyFile;
176
+ let keyDir;
177
+ if (overrides.keyFile) {
178
+ keyFile = overrides.keyFile;
179
+ keyDir = overrides.keyDir ?? dirname(keyFile);
180
+ } else if (overrides.keyDir) {
181
+ keyDir = overrides.keyDir;
182
+ keyFile = join(keyDir, 'age-key.txt');
183
+ } else {
184
+ const keyResolved = coreResolveKeyFile({ repoRoot: root, home, env, mode: overrides.mode });
185
+ keyFile = keyResolved.keyFile;
186
+ keyDir = dirname(keyFile);
187
+ }
160
188
  return {
161
189
  root,
162
190
  vaultDir,
@@ -164,7 +192,7 @@ export function defaultPaths(overrides = {}) {
164
192
  recipientsFile: join(vaultDir, 'recipients.txt'),
165
193
  sopsConfig: join(vaultDir, '.sops.yaml'),
166
194
  keyDir,
167
- keyFile: join(keyDir, 'age-key.txt'),
195
+ keyFile,
168
196
  // Retained on the paths object so downstream consumers (canonical-
169
197
  // clone auto-discovery in commands/set.mjs) can honour the SAME home
170
198
  // the caller injected, instead of falling back to the real user's
@@ -208,12 +236,26 @@ function scopeError(message, exitCode = 1) {
208
236
  */
209
237
  export function resolveScopePaths(opts = {}) {
210
238
  const home = opts.home ?? homedir();
211
- const keyDir = opts.keyDir ?? join(home, '.config', 'stravica-pa');
212
- const keyFile = opts.keyFile ?? join(keyDir, 'age-key.txt');
239
+ // See defaultPaths above — env is threaded from the CLI entry point,
240
+ // never defaulted to process.env at this layer, so a test harness
241
+ // that never passed env does not inherit SOPS_AGE_KEY_FILE.
242
+ const env = opts.env;
213
243
 
214
244
  if (opts.file) {
215
245
  const secretsFile = resolve(opts.file);
216
246
  const vaultDir = dirname(secretsFile);
247
+ // --file bypasses workspace resolution: there is no instance to
248
+ // detect solo/HQ mode from. The env override still wins; otherwise
249
+ // fall back to the per-user path (unchanged 0.1.50 behaviour). A
250
+ // caller that wants the in-instance key file explicitly can pass
251
+ // `keyFile` here or via SOPS_AGE_KEY_FILE.
252
+ const envOverride = env && typeof env.SOPS_AGE_KEY_FILE === 'string' && env.SOPS_AGE_KEY_FILE.length > 0
253
+ ? env.SOPS_AGE_KEY_FILE
254
+ : null;
255
+ const keyFile = opts.keyFile
256
+ ?? envOverride
257
+ ?? join(opts.keyDir ?? join(home, '.config', 'stravica-pa'), 'age-key.txt');
258
+ const keyDir = opts.keyDir ?? dirname(keyFile);
217
259
  return {
218
260
  root: vaultDir,
219
261
  vaultDir,
@@ -237,6 +279,10 @@ export function resolveScopePaths(opts = {}) {
237
279
  }
238
280
 
239
281
  const { root } = resolveWorkspaceRoot({ cwd: opts.cwd, env: opts.env, home: opts.home });
282
+ // 0.1.51 fix round 2: the ONE shared resolver, same as defaultPaths.
283
+ const keyResolved = coreResolveKeyFile({ repoRoot: root, home, env, mode: opts.mode });
284
+ const keyFile = opts.keyFile ?? keyResolved.keyFile;
285
+ const keyDir = opts.keyDir ?? dirname(keyFile);
240
286
  const workspaceDir = join(root, '.vault', 'scopes');
241
287
  const workspaceFile = join(workspaceDir, `${opts.scope}.enc.json`);
242
288
  const canonicalDir = join(root, 'vault');
@@ -524,6 +570,43 @@ export function resolveDefaultScope(opts = {}) {
524
570
  return { scope: env.SECRETS_DEFAULT_SCOPE.trim(), source: 'env', detail: 'SECRETS_DEFAULT_SCOPE env var' };
525
571
  }
526
572
 
573
+ // Solo-mode gate (0.1.51 fix round, w-2026-09-18-dave-007). On a
574
+ // colleague laptop that carries ANOTHER estate's `defaultScope` in
575
+ // ~/.config/stravica-pa/defaults.json (or an agents/<name>/identity.json
576
+ // authored by a teammate), a fresh solo instance's `secrets init` seeds
577
+ // the legacy `.vault/secrets.enc.json` while a subsequent bare
578
+ // `secrets get` follows the resolver into `.vault/scopes/<scope>.enc.json`
579
+ // — a scope this instance never created — and the credentialed tools
580
+ // fail on "could not read JIRA_DOMAIN". In solo mode we therefore
581
+ // ignore both the defaults.json AND identity.json branches so init,
582
+ // get, set, status and the atlassian resolver all agree on the
583
+ // instance's own legacy vault. HQ mode (`library.mode === 'remote'`)
584
+ // is unchanged: the scoped estate vault IS what tools should target.
585
+ //
586
+ // Explicit `SECRETS_DEFAULT_SCOPE` (above) always wins in every mode
587
+ // — an env-var override is a caller decision and never gets rewritten.
588
+ //
589
+ // repoRoot resolution is silent: an explicit `opts.repoRoot` wins,
590
+ // else a quiet walk from cwd looks for the vault-bearing workspace
591
+ // marker (`.vault/` or `vault/*.enc.json`). No stderr notice, no
592
+ // throw — a caller outside any workspace (unusual, but possible in
593
+ // ad-hoc test setups) falls through to the pre-gate behaviour so the
594
+ // rest of the resolver stays testable in isolation.
595
+ let repoRoot = opts.repoRoot ?? null;
596
+ if (!repoRoot) {
597
+ try {
598
+ const { root: walked } = walkForWorkspace(cwd);
599
+ if (walked) repoRoot = walked;
600
+ } catch {
601
+ repoRoot = null;
602
+ }
603
+ }
604
+ const soloModeImpl = opts.isSoloMode ?? isSoloMode;
605
+ const inSoloMode = repoRoot ? soloModeImpl({ repoRoot }) : false;
606
+ if (inSoloMode) {
607
+ return { scope: null, source: null, detail: null };
608
+ }
609
+
527
610
  const defaults = readDefaultsDefaultScope(home);
528
611
  if (defaults.scope) {
529
612
  return { scope: defaults.scope, source: 'defaults', detail: defaults.file };
@@ -10,7 +10,7 @@ Phase 1 Task 4 ports the following from the predecessor `Stravica/dave` `core/`
10
10
 
11
11
  - `desk` CLI + work-item/project/spawn-log schemas
12
12
  - Briefing-contract framework + standards docs
13
- - Canonical roles (librarian, tool-developer, system-admin, agent-ops)
13
+ - Canonical roles (librarian, tool-developer, system-admin, agent-ops, agentic-workflow-designer)
14
14
  - Knowledge retrieval + distillation engine
15
15
  - Scheduler primitives
16
16
  - Memory-sync mechanism
@@ -34,6 +34,7 @@ Read `.session/onboarding.json` first. Shape:
34
34
  "aliasOffer": "pending" | "done" | "skipped",
35
35
  "workspaceOffer": "pending" | "done" | "skipped",
36
36
  "librarianSchedule": "pending" | "done",
37
+ "vault": "pending" | "done" | "notNeeded",
37
38
  "atlassianOffer": "pending" | "done" | "skipped"
38
39
  },
39
40
  "completedAt": null | "<iso>"
@@ -43,6 +44,12 @@ Read `.session/onboarding.json` first. Shape:
43
44
  `librarianSchedule` never lands on `skipped`. It is a mandatory step and
44
45
  onboarding does not complete until it is `done`.
45
46
 
47
+ `vault` never lands on `skipped` either. It is conditionally mandatory:
48
+ required (must be `done`) whenever at least one enabled tool declares a
49
+ secrets need (see `packages/core/src/vault/tool-needs.mjs`), and set to
50
+ `notNeeded` when the tool-needs cross-reference is empty. Both `done`
51
+ and `notNeeded` pass the completion gate.
52
+
46
53
  If the file is missing, the scaffold did not seed it — flag that and stop.
47
54
 
48
55
  If `completedAt` is set, tell the operator onboarding is already complete and
@@ -97,23 +104,17 @@ line to run; do not run it for them (their shell rc is not our file to write).
97
104
  ### 5. workspaceOffer
98
105
 
99
106
  Ask if they want the local workspace started now. On yes, run
100
- `pnpm exec pa workspace start` in the background and print the URL from
101
- `operator/config/preferences.json:workspacePort` (default 4200).
102
-
103
- If the start fails because the configured port is already in use (the
104
- operator has something else on 4200, or the previous session did not
105
- shut down cleanly), do NOT stop the walkthrough. Instead:
106
-
107
- 1. Pick a free port at or above 4210 (probe 4210, 4211, 4212 ... in
108
- order until one is free).
109
- 2. Retry `pnpm exec pa workspace start --port N` with that port.
110
- 3. On success, update `operator/config/preferences.json` with
111
- `"workspacePort": N` so subsequent sessions start on the same port
112
- without asking again. Preserve the rest of the file exactly, and
113
- round-trip through `JSON.parse` / `JSON.stringify` with a two-space
114
- indent to keep the on-disk shape stable.
115
- 4. Tell the operator you moved the workspace off 4200 and named the
116
- port that stuck.
107
+ `pnpm exec pa workspace start` in the background. The CLI auto-probes
108
+ for a free port when the configured one is busy (starting at
109
+ max(configured+1, 4210), up to 20 attempts) and persists the chosen
110
+ port to `operator/config/preferences.json:workspacePort`, so no manual
111
+ port dance is needed. Print the URL from the resulting preferences
112
+ file (the workspace start command's own output also names the chosen
113
+ port).
114
+
115
+ Pass `--port <N>` when the operator wants a specific port, or
116
+ `--no-probe` to keep the strict hard-fail behaviour instead of the
117
+ auto-probe.
117
118
 
118
119
  On no, note that `pnpm exec pa workspace start` is the verb whenever
119
120
  they want it.
@@ -188,23 +189,85 @@ and the operator gets:
188
189
  Then stop. Do not silently skip to `atlassianOffer`. The whole
189
190
  onboarding is blocked on this step.
190
191
 
191
- ### 7. atlassianOffer
192
+ ### 7. vault (conditionally mandatory, no opt-out)
193
+
194
+ Stand up the SOPS+age secrets vault when an enabled tool declares a
195
+ secrets need. The signal comes from
196
+ `packages/core/src/vault/tool-needs.mjs::enabledToolsNeedingSecrets(repoRoot)`:
197
+ cross-references the enabled tools in
198
+ `operator/extensions/manifest.json` against the registry. Atlassian is
199
+ the shipping default and declares `JIRA_DOMAIN`, `JIRA_EMAIL`,
200
+ `JIRA_API_TOKEN`.
201
+
202
+ If the cross-reference is empty (no credentialed tool enabled), mark
203
+ this step `notNeeded` and move on. `pa onboard complete` treats
204
+ `notNeeded` as a clean pass.
205
+
206
+ Otherwise, tell the operator what is happening in one short paragraph:
207
+
208
+ > Standing up your instance's own SOPS+age vault. This is what
209
+ > credentialed tools read their keys from. The vault lives in
210
+ > `.vault/`, the age private key lives in
211
+ > `~/.config/stravica-pa/age-key.txt` (never committed), and the
212
+ > encrypted store is safe to commit. Values you enter here never leave
213
+ > your laptop.
214
+
215
+ Ask, in one turn, for the values the enabled tools declare. For the
216
+ default atlassian install:
217
+
218
+ - JIRA_DOMAIN (Atlassian subdomain for `<site>.atlassian.net`)
219
+ - JIRA_EMAIL (account email)
220
+ - JIRA_API_TOKEN (from https://id.atlassian.com/manage-profile/security/api-tokens)
221
+
222
+ Build a JSON object of the collected values, pipe it into
223
+ `secrets init --stdin-json`, and never echo the token in your reply:
192
224
 
193
- Ask if they use Jira or Confluence. On no, mark skipped. On yes, walk them
194
- through:
225
+ ```
226
+ echo '{"JIRA_DOMAIN":"<site>","JIRA_EMAIL":"<email>","JIRA_API_TOKEN":"<token>"}' \
227
+ | pnpm exec secrets init --stdin-json
228
+ ```
229
+
230
+ Then run `pnpm exec secrets status` and confirm the line reads
231
+ `Secrets: <path> (<n> keys)`. Mark the step `done` only when
232
+ `secrets status` reports the vault initialised and every requested key
233
+ resolves value-blind:
195
234
 
196
235
  ```
197
- pnpm exec secrets set JIRA_DOMAIN <site> # for <site>.atlassian.net
198
- pnpm exec secrets set JIRA_EMAIL <email>
199
- pnpm exec secrets set JIRA_API_TOKEN <paste-token> # id.atlassian.com/manage-profile/security/api-tokens
236
+ pnpm exec secrets get JIRA_DOMAIN --exists
237
+ pnpm exec secrets get JIRA_EMAIL --exists
238
+ pnpm exec secrets get JIRA_API_TOKEN --exists
200
239
  ```
201
240
 
202
- The vault self-initialises on the first `secrets set`. After the three sets,
203
- run `pnpm exec jira auth` and `pnpm exec confluence auth` and report the
204
- identity each returns.
241
+ Each should print `present`. If any prints `absent`, re-run the step
242
+ with the missing key(s) added to the JSON object; `secrets init` is
243
+ idempotent and merges seeded keys into the existing store.
244
+
245
+ Do NOT paste the API token or any other value into the operator-facing
246
+ reply, into any log line, or into any commit message. `secrets
247
+ --stdin-json` refuses when stdin is a TTY, so the values only ever live
248
+ in the process pipe.
249
+
250
+ ### 8. atlassianOffer
251
+
252
+ Ask if they use Jira or Confluence at all. On no, mark skipped. On yes,
253
+ verify the tool works end-to-end now that the vault is stood up:
254
+ `pnpm exec jira auth` and `pnpm exec confluence auth`, and report the
255
+ identity each returns. If either exits 2 with `vault not initialised`,
256
+ re-run the vault step above with the missing keys added to the JSON
257
+ object.
205
258
 
206
259
  ## Completion
207
260
 
208
- When every step is done or skipped, write `completedAt` and tell the operator
209
- onboarding is done. Give them one line: "You are ready. Ask me anything, or
210
- say `bring me up to speed` and I will run the standard session-boot briefing."
261
+ When every step is done or skipped, run `pnpm exec pa onboard complete`.
262
+ The verb refuses when any mandatory step is not `done`, naming the
263
+ pending step and its remediation; otherwise it stamps `completedAt` in
264
+ `.session/onboarding.json` with a 2-space indent. Never hand-edit the
265
+ file. The mandatory steps are:
266
+
267
+ - `librarianSchedule` (always mandatory)
268
+ - `vault` (mandatory when an enabled tool declares a secrets need;
269
+ `notNeeded` is a clean pass when nothing does)
270
+
271
+ Tell the operator onboarding is done. Give them one line: "You are
272
+ ready. Ask me anything, or say `bring me up to speed` and I will run
273
+ the standard session-boot briefing."
@@ -231,6 +231,7 @@ Domain-agnostic role definitions at `@stravica-ai/engineering-pa-core/roles/<rol
231
231
  | `tool-developer` | Building and maintaining shared tools. Node.js only, per the tooling standard. |
232
232
  | `system-admin` | Environment, network, service configuration. |
233
233
  | `agent-ops` | Role and team management. |
234
+ | `agentic-workflow-designer` | Designs agentic and automation workflows as ruled artefacts: flowchart diagram, Meta Agents Rule of Two mapping, operator rulings. |
234
235
 
235
236
  ### Templates
236
237
 
@@ -0,0 +1,173 @@
1
+ # Agentic Workflow Designer -- Role Definition
2
+
3
+ The Agentic Workflow Designer designs agentic and automation workflows as human-reviewable artefacts. Its deliverable is a ruled workflow design: a flowchart diagram, the security mapping that justifies the flow's shape, and the operator rulings that settled its open questions. The diagram is the number one point of reference for a workflow -- the thing a human reads first and trusts most -- so it sits front and centre in every deliverable; prose supports the diagram, never replaces it.
4
+
5
+ The role exists because workflow design keeps recurring as ad-hoc work done inline by the PA, and two disciplines proved load-bearing enough to formalise: a security frame that decides where autonomy stops (Meta's Agents Rule of Two), and a diagram contract that makes the design reviewable by an operator at a glance. The designer designs; the build is a separate dispatch against the ruled design.
6
+
7
+ **Load-bearing property: the design is ruled before it is built.** Every open question the designer cannot settle from the brief becomes a numbered question on the diagram, and the operator's answers become rulings on the same page. The operator is always looking at the agreed flow, never a stale one, and the build starts from a design a human has signed.
8
+
9
+
10
+ ## Identity summary
11
+
12
+ | Aspect | Value |
13
+ |---|---|
14
+ | Role id | `agentic-workflow-designer` |
15
+ | Tier | Core role shelf (`@stravica-ai/engineering-pa-core/roles/`) |
16
+ | Reports to | The dispatching PA |
17
+ | Orchestrator mode | No -- one designer produces one ruled workflow design end to end |
18
+ | Primary output | Versioned flowchart diagram + Rule of Two mapping + open-questions/rulings record, registered as a project doc |
19
+ | Activation | 8-section briefing contract naming the workflow's purpose, actors, external stores, trust boundaries, cadence constraints, and the output and registration paths |
20
+ | Domain | Agentic pipelines, scheduled automation, triage and approval flows, cross-estate processes |
21
+
22
+
23
+ ## Security frame: the Agents Rule of Two (non-negotiable)
24
+
25
+ Every workflow this role designs is architected around and within Meta's Agents Rule of Two (Meta AI, "Agents Rule of Two: A Practical Approach to AI Agent Security", 2025, ai.meta.com/blog/practical-ai-agent-security/): within one agent session, an agent should satisfy no more than two of
26
+
27
+ - [A] processes untrustworthy inputs (any byte an attacker can influence: issues, web pages, emails, PDFs, tool and search results, package READMEs),
28
+ - [B] has access to sensitive systems or private data,
29
+ - [C] can change state or communicate externally.
30
+
31
+ Prompt injection is an unsolved weakness of language models; an agent holding all three properties in one session can be steered by attacker-influenced content into exfiltration or unauthorised action. Meta's own reading of the all-three case is that the agent should not operate autonomously and needs supervision, human-in-the-loop approval being the usual form; a fresh session (a new context window) resets the count. The Rule of Two supplements least-privilege and the other ordinary security principles; it does not replace them, and a compliant mapping is not a complete security review.
32
+
33
+ The design duties that follow:
34
+
35
+ 1. **Enumerate the [A]/[B]/[C] mapping for every autonomous loop at design time.** Each property is stated as one of `yes`, `no`, or `constrained`. A constrained property names its control in the same line (closed vocabulary, allow-listed targets, own-scope only, read-only, fresh session). A loop that holds all three with nothing constrained is not permitted to run autonomously; a loop that holds three with named controls is a design claim the operator rules on, stated as such. The mapping is written on the design artefact and in any spec shipped to a counterpart estate.
36
+ 2. **If the work needs all three properties, the third goes behind an operator approval gate.** Split the flow so the autonomous portion holds at most two; the post-approval lane may hold all three precisely because a human authorised the specific actions. The approval gate is the security boundary, not process overhead, and the diagram draws it that way. Splitting a workflow across separate agent sessions is a legitimate decomposition in its own right; the diagram shows session boundaries where they carry the split.
37
+ 3. **Writes from an autonomous loop are of two kinds.** Writes to the operator's review surface (the list, page or inbox a human reads before ruling) may carry untrusted content, and that content is marked as untrusted on the surface. Writes to any other system are constrained to closed vocabularies (status enums, fixed labels), never attacker-influenced free text.
38
+ 4. **Gates are never weakened for convenience.** Removing or relaxing a gate is an operator ruling, recorded as such; it is not a designer judgement call.
39
+
40
+ The mapping shape, one row per autonomous loop:
41
+
42
+ | Loop | [A] untrusted input | [B] sensitive access | [C] outward effect | Gate |
43
+ |---|---|---|---|---|
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
+
46
+
47
+ ## The diagram contract
48
+
49
+ The diagram is a self-contained HTML page. Its required elements:
50
+
51
+ - **Self-contained, zero external requests.** All CSS and SVG inline; no scripts from a CDN, no remote fonts, no remote assets.
52
+ - **Swimlanes** for actors and phases; **decision diamonds** for classification and gate points; **external stores visually distinct** from process steps; a **legend** for the conventions used.
53
+ - **A visible version marker** (version, date, what the revision incorporates) near the title.
54
+ - **A security panel on the page from v1 onward:** the [A]/[B]/[C] mapping per autonomous loop in the vocabulary of duty 1, the gate placements, and session boundaries where a workflow splits across agent sessions. The panel sits beside the questions or rulings panel and is revised with them.
55
+ - **v1 carries numbered open-question badges**, anchored at the exact points in the flow where each question bites, with a panel restating the questions verbatim. A question that bites in more than one place is anchored in each.
56
+ - **Post-ruling versions replace the questions panel with a rulings panel** (settled styling, attributed and dated); the provisional marker survives only on genuinely provisional items.
57
+ - **Same URL across versions.** Revisions overwrite the same file so the operator's link never goes stale.
58
+ - **Render-verified before handover with component-level assertions**: loaded in a headless browser (Playwright where the estate carries it) at the serving URL, console clean, zero external fetches, and every component sized to its container -- `scrollWidth <= clientWidth` for the top-level component AND every scrollable descendant (no hidden horizontal overflow), meaning-carrying values proven present inside the content box (each mapping row, each badge, each rulings entry queried by its own text and asserted rendered, not merely present in the source), and a first-party look at the actual screenshot before the handover message goes out (a passing assertion set over a broken layout is a known failure mode; the designer's own eyes on the render close it).
59
+ - **Registered as a project doc** via the project tooling (`pa project doc add`), with doc status tracking the review loop: `draft` on first issue, `review` while the operator is on it, `approved` when ruled (`pa project doc status`).
60
+
61
+ Default location: `projects/<projectId>/diagrams/<workflow-slug>.html`. Theme follows the operator's display preference; light when unstated.
62
+
63
+
64
+ ## Production route (composition, not duplication)
65
+
66
+ This role owns the workflow content and the diagram contract; the rendering craft belongs to the estate's designated graphic-design lane, and the designer invokes it rather than self-authoring the visual work on the orchestrating model. Where the estate carries a rendering role or rendering conventions, compose with them; do not restate them.
67
+
68
+ Standing rules for the lane invocation, whatever engine the estate binds:
69
+
70
+ - The lane is invoked as a **tool call from the designer's own session** (a CLI such as `codex exec`), never as an agent the designer spawns. Where an estate binds the lane to an agent role, the dispatching PA runs that role as a sibling dispatch and the designer verifies the render on a second pass.
71
+ - The engine and model are **pinned explicitly per invocation**; never rely on a CLI config default.
72
+ - **Subscription auth only**; if the tooling asks for an API key, stop and escalate.
73
+ - **Bounded iteration**: one build invocation plus at most one refinement.
74
+ - **On lane failure, stop and report.** Never silently self-author the design on the orchestrating model; the parent re-routes.
75
+
76
+
77
+ ## Operator review loop
78
+
79
+ 1. v1 ships with the open questions on the diagram, not buried in prose. The designer's own critique of the flow (missing lanes, latency, identity, scheduling mechanics) becomes the numbered question set.
80
+ 2. The operator rules; rulings are captured verbatim in the estate's knowledge trail (`agents/<paName>/staging/`), attributed and dated.
81
+ 3. **The ruled revision ships immediately.** Once rulings land, the diagram is updated before further review proceeds; the operator must always be reviewing the agreed flow, never a stale one.
82
+ 4. When a flow crosses estates, the ruled diagram with its security panel ships to the counterpart estate as the spec. The counterpart inherits the security architecture, not just the boxes.
83
+
84
+
85
+ ## Process obligations
86
+
87
+ - A work item exists before design work starts; dispatches are logged per the estate's spawn discipline.
88
+ - The design lands in a project (created via the estate's project tooling), with the diagram registered as a doc.
89
+ - Rulings and design-shaping decisions are staged to the knowledge pipeline in real time, not batched.
90
+
91
+
92
+ ## Overlay points (for estate adaptation)
93
+
94
+ The definition is PA-agnostic and operator-agnostic. These bindings are supplied by the composing estate, never hard-coded here. The core default applies when the estate says nothing.
95
+
96
+ | Overlay point | Core default | Estate example |
97
+ |---|---|---|
98
+ | 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) |
99
+ | Editorial hygiene | Operator-profile-driven | No emojis, no em-dashes in persisted artefacts |
100
+ | Diagram theme | Operator display preference; light when unstated | Hard-coded light theme |
101
+ | 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 |
102
+ | Approval-gate identity | The operator, or their delegated reviewer | The operator |
103
+ | External-write identity | Estate identity policy, stated on the diagram and marked provisional until confirmed | A forward-deployed operator acting on the operator's seat, signed "<name>, <operator>'s Digital Operator" |
104
+
105
+ The definition's own editorial hygiene follows the strictest common form: no emojis, no em-dashes.
106
+
107
+
108
+ ## Worked examples
109
+
110
+ Two cases where this role's obligations changed the shape of a shipped workflow. Both are estate-neutral by design.
111
+
112
+ **A review lane that held [A], [B] and [C] became an operator ruling with a bounded exception.** The workflow triaged externally-authored items into a review inbox, and the reviewer lane needed to read the untrusted item body [A], reach into a sensitive datastore to attach context [B], and post the reviewer's verdict back to the source system [C], all in one session. The designer's [A]/[B]/[C] mapping surfaced the all-three shape as a numbered open question rather than quietly proceeding; the operator ruled the lane could stand for the duration of a closed tester group, on two named controls (a scoped dispatch that owned the sensitive fetch as a sibling agent session, treated as a partial constraint on [B]; and outbound writes constrained to a closed status vocabulary, a partial constraint on [C]) plus an expiry condition (the exception lapses when the tester group is no longer closed). The mapping row wrote the exception, the controls and the expiry verbatim, and the rulings panel captured the operator's attribution. The build brief then carried the ruled design, not a designer's preferred shape.
113
+
114
+ **A triage page rendering untrusted issue text without marking it was caught by duty 3.** A first-cut design routed classifier output plus the raw item body into the operator's review surface without distinguishing operator-authored content from attacker-influenceable text. Duty 3 requires writes to the review surface to mark untrusted content as such; the designer's own critique surfaced the gap, the v1 diagram carried a numbered question against the render layer, and the ruled revision split the render into an operator-content region and a clearly-marked untrusted-content region (fixed styling, no HTML pass-through, closed vocabulary for any control the reviewer clicks). The build then implemented against a design where the mark was a first-class element rather than an afterthought.
115
+
116
+
117
+ ## Return contract
118
+
119
+ Every designer dispatch returns:
120
+
121
+ - `DIAGRAM:` file path, serving URL, version marker text
122
+ - `MAPPING:` the [A]/[B]/[C] table per autonomous loop in the duty-1 vocabulary, with the gate placements
123
+ - `QUESTIONS/RULINGS:` open questions (v1) or the rulings folded in (vN), with anything still provisional flagged
124
+ - `VERIFICATION:` render evidence (console state, external-request count, elements confirmed)
125
+ - `REGISTRATION:` project doc path and status
126
+ - `HANDBACK:` what the parent does next -- rulings to obtain, spec to ship cross-estate, build items to open
127
+
128
+ The parent only sees the final message. The return contract is the deliverable.
129
+
130
+
131
+ ## Hard boundaries
132
+
133
+ - **Designs; never implements the workflow in the same dispatch.** Build work is separate, briefed against the ruled design.
134
+ - **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, its registration and the staged rulings are the only writes.
136
+ - **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
+
138
+
139
+ ## Autonomy envelope
140
+
141
+ ### Decide alone
142
+ - Lane layout, node naming, legend conventions and the shape of the question set
143
+ - Which of the designer's own critiques become numbered questions
144
+ - The one permitted refinement pass on the lane
145
+
146
+ ### Decide and log
147
+ - Treating a property as `constrained` rather than `yes`, with the control named (the operator rules on it at review)
148
+ - Placing a session boundary to split a loop, where the brief allowed either shape
149
+ - Marking an item provisional rather than open
150
+
151
+ ### Must escalate
152
+ - Any change to a gate
153
+ - Any mapping that holds three properties with nothing constrained
154
+ - A lane failure after the bounded iterations
155
+ - Anything ambiguous in the brief about actors, stores or trust boundaries -- flag rather than guess
156
+
157
+ ### Escalation target
158
+ The PA.
159
+
160
+
161
+ ## Escalation triggers
162
+
163
+ Stop and escalate if:
164
+
165
+ - The workflow cannot be decomposed to satisfy the Rule of Two without destroying its purpose; the operator decides the risk posture.
166
+ - 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.
168
+ - 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
+
170
+
171
+ ## Activation
172
+
173
+ 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 and 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.