@rubytech/create-sitedesk-code 0.1.524 → 0.1.526
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/__tests__/samba-provision.test.js +46 -1
- package/dist/__tests__/websockify-bind-wiring.test.js +47 -0
- package/dist/index.js +39 -1
- package/dist/samba-provision.js +37 -0
- package/package.json +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +8 -7
- package/payload/platform/plugins/admin/skills/superpowers-sprint/SKILL.md +26 -0
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +12 -0
- package/payload/platform/plugins/memory/PLUGIN.md +10 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js +5 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.js +141 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js +6 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.d.ts +45 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.js +122 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-by-name.test.js +26 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-by-name.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.js +201 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +89 -33
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +6 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.js +148 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +83 -6
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +90 -5
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js +173 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +73 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +194 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +25 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +81 -8
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js +39 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +9 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
- package/payload/platform/scripts/__tests__/check-canonical-tool-names.test.sh +162 -0
- package/payload/platform/scripts/__tests__/check-no-task-id-leaks.test.sh +53 -0
- package/payload/platform/scripts/__tests__/task-id-citation.test.sh +72 -0
- package/payload/platform/scripts/check-canonical-tool-names.mjs +36 -4
- package/payload/platform/scripts/check-no-task-id-leaks.mjs +80 -62
- package/payload/platform/scripts/lib/canonical-tool-names.mjs +130 -26
- package/payload/platform/scripts/lib/task-id-citation.mjs +67 -0
- package/payload/platform/scripts/vnc.sh +45 -1
- package/payload/server/{chunk-VUXKXOJU.js → chunk-7MGR5M6G.js} +4 -2
- package/payload/server/{manager-4TJK3YOY.js → manager-6XOWHREK.js} +1 -1
- package/payload/server/server.js +10 -4
- package/payload/platform/.docs/search-surface-contract.md +0 -58
- package/payload/platform/docs/superpowers/plans/2026-06-02-task-610-follower-202-retry.md +0 -372
- package/payload/platform/docs/superpowers/plans/2026-06-04-public-agent-knowledge-delivery.md +0 -230
- package/payload/platform/docs/superpowers/plans/2026-06-23-account-filesystem-schema.md +0 -544
- package/payload/platform/docs/superpowers/plans/2026-07-11-task-1557-operator-on-behalf-preference-attribution.md +0 -542
- package/payload/platform/docs/superpowers/plans/2026-07-13-account-schema-ontology-projection.md +0 -547
- package/payload/platform/docs/superpowers/plans/2026-07-14-graph-top-level-labels-ontology-single-source.md +0 -458
- package/payload/platform/docs/superpowers/plans/2026-07-17-task-1736-retire-mcp-stderr-tee.md +0 -397
- package/payload/platform/docs/superpowers/plans/2026-07-18-graph-native-ledger.md +0 -807
- package/payload/platform/docs/superpowers/plans/2026-07-18-ledger-write-path-fix.md +0 -109
- package/payload/platform/docs/superpowers/plans/2026-07-20-storage-pages-create.md +0 -82
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1704-data-portal-standing-audit.md +0 -673
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +0 -1244
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +0 -462
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +0 -321
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1829-platform-ui-typecheck.md +0 -830
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +0 -2177
- package/payload/platform/docs/superpowers/plans/2026-07-21-graph-caption-resolver.md +0 -124
- package/payload/platform/docs/superpowers/plans/2026-07-21-task-1877-bash-schema-enforcement.md +0 -107
- package/payload/platform/docs/superpowers/plans/2026-07-21-task-1887-reconcile-allowed-top-level.md +0 -205
- package/payload/platform/docs/superpowers/plans/2026-07-22-task-1899-reconcile-report-only.md +0 -748
- package/payload/platform/docs/superpowers/plans/2026-07-22-task-1910-two-way-portal-exchange.md +0 -1716
- package/payload/platform/docs/superpowers/plans/2026-07-23-task-1930-adherence-enforcement.md +0 -422
- package/payload/platform/docs/superpowers/plans/2026-07-24-task-1942-subaccount-switcher-brand-head.md +0 -327
- package/payload/platform/docs/superpowers/plans/2026-07-25-task-1974-uploads-intake-inbox.md +0 -235
- package/payload/platform/docs/superpowers/plans/2026-07-25-task-1976-intra-folder-hygiene.md +0 -215
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-1902-account-owned-entry-declarations.md +0 -555
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-1926-portal-push-concurrency-guard.md +0 -482
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-2023-sidebar-sessions-async-reads.md +0 -394
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2016-disabled-agent-routing.md +0 -624
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2028-declared-file-write-deny.md +0 -303
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2052-account-machinery-write-fence.md +0 -250
- package/payload/platform/docs/superpowers/plans/2026-07-28-task-2097-email-signature-set.md +0 -65
- package/payload/platform/docs/superpowers/specs/2026-06-02-task-610-follower-202-retry-design.md +0 -116
- package/payload/platform/docs/superpowers/specs/2026-06-23-account-filesystem-schema-design.md +0 -170
- package/payload/platform/docs/superpowers/specs/2026-07-11-task-1557-operator-on-behalf-preference-attribution-design.md +0 -90
- package/payload/platform/docs/superpowers/specs/2026-07-13-account-schema-ontology-projection-design.md +0 -178
- package/payload/platform/docs/superpowers/specs/2026-07-14-graph-top-level-labels-ontology-single-source-design.md +0 -119
- package/payload/platform/docs/superpowers/specs/2026-07-17-task-1736-retire-mcp-stderr-tee-design.md +0 -128
- package/payload/platform/docs/superpowers/specs/2026-07-18-graph-native-ledger-design.md +0 -241
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1704-data-portal-standing-audit-design.md +0 -177
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +0 -201
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +0 -217
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +0 -80
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1820-cpu-triage-admin-tools-design.md +0 -97
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1829-platform-ui-typecheck-design.md +0 -163
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +0 -163
- package/payload/platform/docs/superpowers/specs/2026-07-22-task-1899-reconcile-report-only-design.md +0 -121
- package/payload/platform/docs/superpowers/specs/2026-07-22-task-1910-two-way-portal-exchange-design.md +0 -103
- package/payload/platform/docs/superpowers/specs/2026-07-23-task-1930-adherence-enforcement-design.md +0 -81
- package/payload/platform/docs/superpowers/specs/2026-07-24-task-1942-subaccount-switcher-brand-head-design.md +0 -35
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-1902-account-owned-entry-declarations-design.md +0 -232
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-1926-portal-push-concurrency-guard-design.md +0 -200
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-2014-ui-suite-nondeterminism-design.md +0 -169
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2016-disabled-agent-routing-design.md +0 -139
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2028-declared-file-write-deny-design.md +0 -127
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2052-account-machinery-write-fence-design.md +0 -150
- package/payload/platform/docs/superpowers/specs/2026-07-28-task-2097-email-signature-set-design.md +0 -155
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
# Task 2016 — a disabled agent must leave the admin's routing table, and read as disabled rather than missing
|
|
2
|
-
|
|
3
|
-
Design, 2026-07-27. Depends on Task 1996 (landed).
|
|
4
|
-
|
|
5
|
-
## The defect
|
|
6
|
-
|
|
7
|
-
Task 1996 stops dispatch by moving an agent file out of `<accountDir>/specialists/agents/`,
|
|
8
|
-
the directory `spawn-context.ts` reads to build the spawn manifest. Two surfaces still
|
|
9
|
-
believe the file is there.
|
|
10
|
-
|
|
11
|
-
`platform/scripts/lib/agents-md-bootstrap.sh:42-47` only ever appends. A line matching
|
|
12
|
-
`^- \*\*specialists:<name>\*\*:` in `agents/admin/AGENTS.md` is preserved verbatim and a
|
|
13
|
-
missing one is appended; nothing is ever removed. So the admin's routing prose keeps
|
|
14
|
-
advertising a specialist the operator switched off, and dispatch fails inside a turn
|
|
15
|
-
against a file that is gone.
|
|
16
|
-
|
|
17
|
-
`platform/plugins/admin/mcp/src/index.ts:520-533` walks the AGENTS.md entries against
|
|
18
|
-
`specialists/agents/` and calls a name with no file `missing file`. An operator action is
|
|
19
|
-
rendered as an install fault.
|
|
20
|
-
|
|
21
|
-
## What the fix delivers
|
|
22
|
-
|
|
23
|
-
1. A name the operator disabled has no routing line.
|
|
24
|
-
2. Re-enabling restores it.
|
|
25
|
-
3. `system-status` reports `disabled` and `missing file` as different things, and counts
|
|
26
|
-
them separately.
|
|
27
|
-
|
|
28
|
-
## Surface 1 — `agents-md-bootstrap.sh` reconciles
|
|
29
|
-
|
|
30
|
-
The store is `<accountDir>/agents-disabled.json`, `{ "disabled": ["<basename>.md", …] }`,
|
|
31
|
-
written whole at 0600 by `platform/ui/server/routes/admin/agents.ts`. The bootstrap reads
|
|
32
|
-
it with the same python3 extractor `provision-account-dir.sh:264-276` already uses, which
|
|
33
|
-
accepts only strings ending `.md` with no `/` and no `..`.
|
|
34
|
-
|
|
35
|
-
**The name rule.** A store entry maps to a routing line by stripping `.md`. One rule, no
|
|
36
|
-
branch: all twelve files under `platform/templates/specialists/agents/` have `name:` equal
|
|
37
|
-
to their filename, the disable route keys on `slug` equal to the basename, and the status
|
|
38
|
-
walker at `index.ts:526` already maps a name back to `<name>.md`.
|
|
39
|
-
|
|
40
|
-
**The reconcile rule.** A name in the store gets no routing line. It applies at both points
|
|
41
|
-
in the script: an existing line for that name is removed, and the append loop skips that
|
|
42
|
-
name. Stating it once and applying it uniformly is what makes the invariant hold in the
|
|
43
|
-
drift state the standing `op=agent-parity` check exists to catch (store names it, file is
|
|
44
|
-
still live) rather than only on the happy path.
|
|
45
|
-
|
|
46
|
-
**An unreadable store withholds nothing.** `provision-account-dir.sh:293` already prints
|
|
47
|
-
`specialists agents-disabled-store-unreadable — withholding nothing` and carries on, so
|
|
48
|
-
the prose posture matches the file-withholding posture that runs immediately before it.
|
|
49
|
-
|
|
50
|
-
**Observability.** The existing line stays byte-identical, because the current suite
|
|
51
|
-
asserts its counts:
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
[admin-bootstrap] AGENTS.md specialists=<n> appended=<m> existing=<k>
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
A second line is added:
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
[agents-md] op=specialist-lines account=<id8> kept=<n> withheld=<comma-list|none|store-unreadable>
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
`id8` is the first eight characters of the account directory's basename. `kept` counts the
|
|
64
|
-
`- **specialists:` lines in AGENTS.md after the run. `withheld` names the store's entries,
|
|
65
|
-
sorted, as names rather than basenames; `none` when the store is absent or empty; the
|
|
66
|
-
literal `store-unreadable` when it is present but does not parse.
|
|
67
|
-
|
|
68
|
-
## Surface 2 — `system-status` separates disabled from missing
|
|
69
|
-
|
|
70
|
-
The classification at `index.ts:520-533` moves into a new
|
|
71
|
-
`platform/plugins/admin/mcp/src/specialist-registry.ts`, beside `skill-resolution.ts` and
|
|
72
|
-
`store-skill-core.ts`, which are the same shape: a module `index.ts` imports and a
|
|
73
|
-
`node:test` suite covers. It exports one function taking the AGENTS.md text, the
|
|
74
|
-
specialists directory, the account directory and whether Chrome is up, and returning the
|
|
75
|
-
per-name lines plus the two counts.
|
|
76
|
-
|
|
77
|
-
Status, in order:
|
|
78
|
-
|
|
79
|
-
- the file exists in `specialists/agents/` → `ok`, except `personal-assistant` with Chrome
|
|
80
|
-
down, which keeps today's `degraded — chrome not running`;
|
|
81
|
-
- otherwise the name is in the store → `disabled (operator)`;
|
|
82
|
-
- otherwise → `missing file`.
|
|
83
|
-
|
|
84
|
-
The header becomes `<n> registered, missing=<x> disabled=<y>`. An unreadable store is
|
|
85
|
-
tolerated the same way the bootstrap tolerates it: no names classify as disabled, and the
|
|
86
|
-
absent file stays `missing file`, which is the honest answer when the store cannot be read.
|
|
87
|
-
|
|
88
|
-
With surface 1 in place a disabled agent has no AGENTS.md line at all, so in the steady
|
|
89
|
-
state `disabled=0`. The classification earns its place in the drift window: an account
|
|
90
|
-
disabled before this change carries the stale line until a provisioning run or a
|
|
91
|
-
disable/enable call reconciles it, and that is exactly when the operator needs the line to
|
|
92
|
-
read `disabled` rather than `missing file`.
|
|
93
|
-
|
|
94
|
-
## Surface 3 — the disable path reconciles immediately
|
|
95
|
-
|
|
96
|
-
`agents.ts` currently leaves AGENTS.md untouched, so prose is only correct after the next
|
|
97
|
-
provisioning run. After the file move and the store write, both handlers exec
|
|
98
|
-
`bash <PLATFORM_ROOT>/scripts/lib/agents-md-bootstrap.sh <accountDir>` with a timeout.
|
|
99
|
-
`platform/scripts` ships in the payload (`packages/create-maxy-code/scripts/bundle.js:318`
|
|
100
|
-
copies the whole `platform` tree minus the listed exclusions), and `claude-info.ts:33`
|
|
101
|
-
is the precedent for `execFileSync` in an admin route.
|
|
102
|
-
|
|
103
|
-
Enable needs no separate restore path. The file is live again by the time the script runs,
|
|
104
|
-
so the append loop puts the line back and the store no longer names it.
|
|
105
|
-
|
|
106
|
-
**Failure posture.** A failed exec does not turn the request into a 500: the file move and
|
|
107
|
-
the store write already happened, and dispatch is already stopped, so reporting the whole
|
|
108
|
-
disable as failed would be false. The handler logs
|
|
109
|
-
`[admin/agents] op=agents-md-reconcile accountId=<id8> slug=<slug> outcome=failed reason="…"`
|
|
110
|
-
and the response carries `routingReconciled: false`, so no caller is told the prose is
|
|
111
|
-
correct when it is not.
|
|
112
|
-
|
|
113
|
-
## Tests
|
|
114
|
-
|
|
115
|
-
**Shell, `platform/scripts/__tests__/agents-md-bootstrap.test.sh`.** Against the existing
|
|
116
|
-
three-specialist temp account: a store naming one of them leaves no line for it and keeps
|
|
117
|
-
the other two, and the `op=specialist-lines` line reports `kept=2 withheld=<that name>`;
|
|
118
|
-
clearing the store restores the line on the next run; a malformed store removes nothing and
|
|
119
|
-
reports `withheld=store-unreadable`. The pre-existing cases run unchanged with no store on
|
|
120
|
-
disk.
|
|
121
|
-
|
|
122
|
-
**Route, `platform/ui/server/routes/admin/__tests__/agents.test.ts`.** The harness mocks
|
|
123
|
-
`PLATFORM_ROOT` to a temp tree; the real bootstrap script is copied into
|
|
124
|
-
`<platformRoot>/scripts/lib/` so the exec runs the shipped code rather than a stub. A
|
|
125
|
-
disable leaves no `specialists:<name>` line in the account's AGENTS.md; the following
|
|
126
|
-
enable puts it back.
|
|
127
|
-
|
|
128
|
-
**MCP, `platform/plugins/admin/mcp/src/__tests__/specialist-registry.test.ts`
|
|
129
|
-
(`node:test`).** A temp account with three AGENTS.md entries: one live file, one named by
|
|
130
|
-
the store, one neither. Asserts `ok`, `disabled (operator)`, `missing file`, and
|
|
131
|
-
`missing=1 disabled=1`. A `missing file` line for a name in the store is a failure.
|
|
132
|
-
|
|
133
|
-
## Out of scope
|
|
134
|
-
|
|
135
|
-
- The disable mechanism, the quarantine directory, and the store format (Task 1996).
|
|
136
|
-
- What AGENTS.md is for, and its append-only posture for anything that is not a specialist
|
|
137
|
-
routing line.
|
|
138
|
-
- The `op=agent-parity` check, which already covers store-versus-directory drift.
|
|
139
|
-
- `AGENTS.md` write permissions, which is Task 2057.
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# Task 2028 — the write guard tells a declared file from an operator bucket
|
|
2
|
-
|
|
3
|
-
Design, 2026-07-27. Lane: Platform / account schema · write guard.
|
|
4
|
-
|
|
5
|
-
## The fault
|
|
6
|
-
|
|
7
|
-
`fs-schema-guard.sh` is the PreToolUse guard for `Write`, `Edit` and `NotebookEdit`.
|
|
8
|
-
It parses the account's ```` ```allowed-top-level ```` fence and blocks a write whose
|
|
9
|
-
first path segment is absent from it. The standing reconcile parses the same fence
|
|
10
|
-
to decide what is not a stray. One list, two meanings.
|
|
11
|
-
|
|
12
|
-
Task 1902 declared eight control-plane files so the reconcile would stop naming
|
|
13
|
-
them. Because names union into that one fence, the guard began allowing
|
|
14
|
-
agent writes to them: measured, the same envelope returns exit 2 against the
|
|
15
|
-
pre-merge schema and exit 0 against the merged one for `wa-channel-bindings.json`,
|
|
16
|
-
`telegram-channel-bindings.json`, `webchat-channel-bindings.json`,
|
|
17
|
-
`canonical-webchat-session.json`, `session-titles.json`, `agents-disabled.json`,
|
|
18
|
-
`calendar-availability.json` and `data-portal.json`.
|
|
19
|
-
|
|
20
|
-
The fence already permits `account.json`, `SCHEMA.md`, `secrets/` and `.claude/` on
|
|
21
|
-
the untouched shipped template, so it is a layout guard, not a trust boundary. This
|
|
22
|
-
task makes the guard enforce the intent the generated region already states: a
|
|
23
|
-
declared file "is written whole by its owner. Neither is operator data."
|
|
24
|
-
|
|
25
|
-
## Design
|
|
26
|
-
|
|
27
|
-
### 1. A second fence, emitted by the merge
|
|
28
|
-
|
|
29
|
-
`account-schema-owned-dirs.py` `merge()` gains a third generated region, written
|
|
30
|
-
with the same strip-then-append shape as the two that exist, so a re-merge is
|
|
31
|
-
idempotent:
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
<!-- declared-files:start -->
|
|
35
|
-
## Declared files (written by platform code, owned by a plugin)
|
|
36
|
-
|
|
37
|
-
...one paragraph of prose...
|
|
38
|
-
|
|
39
|
-
```declared-files
|
|
40
|
-
webchat-channel-bindings.json admin
|
|
41
|
-
wa-channel-bindings.json admin
|
|
42
|
-
data-portal.json cloudflare
|
|
43
|
-
```
|
|
44
|
-
<!-- declared-files:end -->
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
One line per platform-written declared file, in `resolve()` order, as
|
|
48
|
-
`name<TAB>owning-plugin`. The region is emitted only when at least one such file
|
|
49
|
-
is declared.
|
|
50
|
-
|
|
51
|
-
**Two of the eight declared files are agent-written, and are excluded.** The
|
|
52
|
-
review established that `calendar-availability.json` and `data-portal.json` have
|
|
53
|
-
no writer in platform code: `publish-availability.ts:151` and
|
|
54
|
-
`portal-index-push.mjs:52,380` only read them, and the `calendar-site` and
|
|
55
|
-
`data-portal` skills instruct the agent to author them with `Write`
|
|
56
|
-
("there is no separate editor"). Denying them would break the only path that
|
|
57
|
-
creates them. A file declaration may therefore carry `"writtenBy": "agent"`,
|
|
58
|
-
which keeps the name in `allowed-top-level` (so the reconcile still never calls
|
|
59
|
-
it a stray) and out of the declared-files fence (so the guard never denies it).
|
|
60
|
-
The two declarations gain that key, and their descriptions are corrected: both
|
|
61
|
-
named a writer that only reads.
|
|
62
|
-
|
|
63
|
-
The owner column names the *declaring* plugin, which is where the declaration
|
|
64
|
-
lives rather than necessarily the module that writes the file — Task 1902 moved
|
|
65
|
-
declarations onto plugins no brand excludes for reach, so a shared service may
|
|
66
|
-
write a file declared on `admin`. The block message therefore points at the
|
|
67
|
-
owning code and does not promise a plugin tool. `allowed-top-level` is
|
|
68
|
-
not touched, so the reconcile keeps reading exactly what it reads today, and the two
|
|
69
|
-
existing region readers (`platform/lib/account-schema-regions`, the cloudflare
|
|
70
|
-
`schema-exposed-dirs.mjs` copy) both anchor on their own markers and on the
|
|
71
|
-
`allowed-top-level` fence by name, so an appended region is invisible to them.
|
|
72
|
-
|
|
73
|
-
The owning-plugin column exists because the block message has to name the owner,
|
|
74
|
-
and the descriptive region carries the plugin's own description, not its name.
|
|
75
|
-
|
|
76
|
-
### 2. The guard denies
|
|
77
|
-
|
|
78
|
-
`fs-schema-guard.sh` parses the new fence with one awk expression, the same shape it
|
|
79
|
-
already uses for the allowed set. After the top-level check passes and before the
|
|
80
|
-
depth check, a target whose first path segment matches a declared name is blocked:
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
[fs-guard] blocked path=<rel> reason=declared-file
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
plus an operator-visible line naming the owning plugin and pointing the agent at it.
|
|
87
|
-
An account whose SCHEMA.md carries no `declared-files` fence denies nothing, which is
|
|
88
|
-
the same fail-open posture the guard already takes on a missing or empty schema.
|
|
89
|
-
|
|
90
|
-
The match is on the first path segment, not on the whole relative path, so a write to
|
|
91
|
-
`wa-channel-bindings.json/anything` is blocked for the same reason.
|
|
92
|
-
|
|
93
|
-
### 3. The Bash post-hook stays quiet
|
|
94
|
-
|
|
95
|
-
A Bash command that creates a declared file emits no stray feedback. That is the
|
|
96
|
-
behaviour Task 1902 produced and it is kept deliberately, not inherited: the post
|
|
97
|
-
hook fires only on a name that appeared during the command, and a declared file
|
|
98
|
-
already exists on any live account, so a Bash edit to one is invisible there
|
|
99
|
-
regardless of the allowed set. A signal that catches only the first creation and
|
|
100
|
-
misses every subsequent edit is not a boundary. The decision is written into the
|
|
101
|
-
hook's header comment and pinned by a test, so a future edit has to argue with it
|
|
102
|
-
rather than flip it silently.
|
|
103
|
-
|
|
104
|
-
## Testing
|
|
105
|
-
|
|
106
|
-
- `fs-schema-guard.test.sh`: one case per declared file asserting exit 2 with
|
|
107
|
-
`reason=declared-file` and the owner name in the message; an operator bucket write
|
|
108
|
-
under the same schema asserting exit 0; a declared name under an account that
|
|
109
|
-
carries it in `allowed-top-level` but has no `declared-files` fence asserting
|
|
110
|
-
exit 0, which pins the deny to the fence rather than to a list baked into the
|
|
111
|
-
hook.
|
|
112
|
-
- `fs-schema-guard-bash.test.sh`: a Bash command that creates a declared file
|
|
113
|
-
asserting exit 0 and no `[fs-guard-bash]` line.
|
|
114
|
-
- `account-schema-owned-dirs.test.sh`: the merge emits the fence with
|
|
115
|
-
`name<TAB>plugin` for each declared file, emits nothing when no file is declared,
|
|
116
|
-
and a second merge leaves the file byte-identical.
|
|
117
|
-
- Mutation check: remove the guard's deny branch and confirm the per-file cases fail.
|
|
118
|
-
- The reconcile's own suite still reports no declared file as a stray.
|
|
119
|
-
|
|
120
|
-
## Out of scope
|
|
121
|
-
|
|
122
|
-
- The fence's pre-existing permissiveness for `account.json`, `secrets/` and
|
|
123
|
-
`.claude/`. It predates Task 1902 and is a separate decision, filed as
|
|
124
|
-
`.tasks/archive/2052-the-allowed-fence-permits-agent-writes-to-the-account-s-own-machinery.md`
|
|
125
|
-
(renumbered from 2048; landed 2026-07-27).
|
|
126
|
-
- The reconcile. It reads the fence correctly and needs no change.
|
|
127
|
-
- Adding or removing declarations. Task 1902 settled the set.
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# Task 2052 — the write guard denies the account's own machinery
|
|
2
|
-
|
|
3
|
-
Design, 2026-07-27. Lane: Platform / account schema · write guard.
|
|
4
|
-
|
|
5
|
-
## The fault
|
|
6
|
-
|
|
7
|
-
`fs-schema-guard.sh` allows a `Write`, `Edit` or `NotebookEdit` whose first path
|
|
8
|
-
segment is in the account's ```` ```allowed-top-level ```` fence. The shipped
|
|
9
|
-
template lists `account.json`, `SCHEMA.md`, `secrets/`, `.claude/` and `.git`, so
|
|
10
|
-
each is agent-writable. Measured against the untouched shipped template, an
|
|
11
|
-
envelope targeting `account.json` returns exit 0.
|
|
12
|
-
|
|
13
|
-
None of the five is operator data:
|
|
14
|
-
|
|
15
|
-
- `account.json` carries the account identity, role, tier and enabled plugins.
|
|
16
|
-
`admin-user-management/SKILL.md:39` states the position already: "Direct `Edit`
|
|
17
|
-
or `Write` on `account.json` is forbidden by IDENTITY.md doctrine — there is no
|
|
18
|
-
server-side gate, so the doctrine line is the only thing standing between an
|
|
19
|
-
agent slip and a silently corrupted account file." Mutations have dedicated
|
|
20
|
-
tools (`account-update`, `plugin-toggle-enabled`, `admin-add`, `admin-remove`).
|
|
21
|
-
- `SCHEMA.md` is the schema the guard itself parses. A write to it widens the
|
|
22
|
-
fence the guard reads on the next call.
|
|
23
|
-
- `.claude/` holds the account's project-level `settings.json`, which is where
|
|
24
|
-
`provision-account-dir.sh:62,77-143` registers this guard and both Bash hooks. A
|
|
25
|
-
write to it can delete the guard's own registration.
|
|
26
|
-
- `secrets/` holds provisioned credentials.
|
|
27
|
-
- `.git` is the account directory's git repository, created at
|
|
28
|
-
`provision-account-dir.sh:59` so Claude Code discovers `.claude/`.
|
|
29
|
-
|
|
30
|
-
## Decision per name
|
|
31
|
-
|
|
32
|
-
All five are denied to the agent's write tools. No legitimate agent
|
|
33
|
-
`Write`/`Edit`/`NotebookEdit` path exists for any of them:
|
|
34
|
-
|
|
35
|
-
| Name | Who writes it |
|
|
36
|
-
|---|---|
|
|
37
|
-
| `account.json` | `provision-account-dir.sh:310-349` and the account MCP tools |
|
|
38
|
-
| `SCHEMA.md` | `provision-account-dir.sh:40-41` and the owned-dirs merge |
|
|
39
|
-
| `secrets/` | the code that mints each credential; the one skill-instructed path is a Bash append (`cloudflare/skills/data-portal/SKILL.md:161`), which this guard never sees |
|
|
40
|
-
| `.claude/` | `provision-account-dir.sh:26,61-143` |
|
|
41
|
-
| `.git` | git itself |
|
|
42
|
-
|
|
43
|
-
## Design
|
|
44
|
-
|
|
45
|
-
### 1. A third fence, shipped in the template
|
|
46
|
-
|
|
47
|
-
`platform/templates/account-schema/SCHEMA.md` gains a fenced block below the
|
|
48
|
-
allowed-top-level section:
|
|
49
|
-
|
|
50
|
-
````
|
|
51
|
-
```agent-denied-top-level
|
|
52
|
-
account.json the account's identity and settings, changed through the account and admin tools
|
|
53
|
-
SCHEMA.md the schema this guard reads, so a hand edit widens the guard's own fence
|
|
54
|
-
secrets provisioned credentials, written by the code that mints each one
|
|
55
|
-
.claude the agent's own settings and hooks, seeded when the account is provisioned
|
|
56
|
-
.git the account directory's git internals
|
|
57
|
-
```
|
|
58
|
-
````
|
|
59
|
-
|
|
60
|
-
One line per name as `name<TAB>reason`, the same shape as the `declared-files`
|
|
61
|
-
fence. The reason column exists because there is no owning plugin to name here,
|
|
62
|
-
so the block message needs the schema to supply its own explanation.
|
|
63
|
-
|
|
64
|
-
The block is authored in the template rather than generated. The owned-dirs merge
|
|
65
|
-
(`account-schema-owned-dirs.py`) rewrites only its three marker-delimited regions
|
|
66
|
-
and the `allowed-top-level` fence in place, so a fence in the template body
|
|
67
|
-
survives a re-merge untouched. `allowed-top-level` is not modified, so the
|
|
68
|
-
standing reconcile keeps reading exactly what it reads today and still never
|
|
69
|
-
names any of the five as a stray — the one-list-two-meanings fault Task 2028
|
|
70
|
-
fixed is not reintroduced.
|
|
71
|
-
|
|
72
|
-
### 2. The guard denies
|
|
73
|
-
|
|
74
|
-
`fs-schema-guard.sh` parses the new fence with one awk expression, the same shape
|
|
75
|
-
it uses for the other two. The check sits after the top-level check and before
|
|
76
|
-
the declared-file check, and matches on the first path segment, so
|
|
77
|
-
`.claude/settings.json` and `secrets/cloudflare.env` are both blocked. No name is
|
|
78
|
-
in both fences (the declared set is the six platform-written `*.json` files the
|
|
79
|
-
merge emits), so the ordering never changes which reason is reported.
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
[fs-guard] blocked path=<rel> reason=account-machinery
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
The operator-visible line names the segment and carries that name's reason from
|
|
86
|
-
the fence, then points at the code that owns it. No task numbers or internal refs
|
|
87
|
-
appear in it, per the guard's existing rule.
|
|
88
|
-
|
|
89
|
-
An account whose `SCHEMA.md` carries no `agent-denied-top-level` fence denies
|
|
90
|
-
nothing. That is the same fail-open posture the guard already takes on a missing
|
|
91
|
-
schema and on a missing `declared-files` fence.
|
|
92
|
-
|
|
93
|
-
Which accounts lack it is not uniform, and the split matters. The fence lives in
|
|
94
|
-
the template body, and only `provision_account_dir` copies that body
|
|
95
|
-
(`provision-account-dir.sh:40-41`, an unconditional `cp` of the whole template).
|
|
96
|
-
`setup-account.sh:59` calls it once, for the house account. The standing
|
|
97
|
-
all-accounts reconcile merges only the marker-delimited regions. So the house
|
|
98
|
-
account gains the fence on the next install, and a client sub-account, which is
|
|
99
|
-
provisioned once at creation, keeps a fence-less schema indefinitely. Closing
|
|
100
|
-
that is filed as `.tasks/pending/2056-the-account-machinery-deny-fence-never-reaches-an-existing-sub-account.md`,
|
|
101
|
-
following the durable-backfill pattern Task 1683 and Task 1929 already
|
|
102
|
-
established in `setup-account.sh`.
|
|
103
|
-
|
|
104
|
-
### 3. Provision and reconcile are unaffected
|
|
105
|
-
|
|
106
|
-
Both write `SCHEMA.md` and `account.json`, and neither goes through the agent's
|
|
107
|
-
write tools: provisioning is bash (`provision-account-dir.sh`) and the standing
|
|
108
|
-
reconcile is Node (`account-dir-schema-reconcile.ts`). The guard is a PreToolUse
|
|
109
|
-
hook on `Write`/`Edit`/`NotebookEdit` only, so it never sees either writer.
|
|
110
|
-
|
|
111
|
-
### 4. The Bash post-hook stays unchanged
|
|
112
|
-
|
|
113
|
-
`fs-schema-guard-bash-post.sh` reads `allowed-top-level` and the `declared-files`
|
|
114
|
-
fence. This change touches neither, and all five names stay in `allowed-top-level`,
|
|
115
|
-
so the post hook keeps not flagging them. That is the deliberate choice, not an
|
|
116
|
-
inherited one: the post hook fires only on a top-level name that appeared during
|
|
117
|
-
the command, and `account.json`, `SCHEMA.md`, `.claude` and `.git` all exist from
|
|
118
|
-
provision onward, so a signal there would catch nothing on a live account.
|
|
119
|
-
|
|
120
|
-
## Testing
|
|
121
|
-
|
|
122
|
-
`fs-schema-guard.test.sh` gains:
|
|
123
|
-
|
|
124
|
-
- one case per name asserting exit 2 with `reason=account-machinery` and that
|
|
125
|
-
name's reason text present in the message;
|
|
126
|
-
- a nested case for `.claude/settings.json` and `secrets/cloudflare.env`
|
|
127
|
-
asserting exit 2, pinning the segment-0 match;
|
|
128
|
-
- an operator bucket write under the same schema asserting exit 0;
|
|
129
|
-
- a case under a schema that lists the five in `allowed-top-level` but carries no
|
|
130
|
-
`agent-denied-top-level` fence asserting exit 0, which pins the deny to the
|
|
131
|
-
fence rather than to a list baked into the hook;
|
|
132
|
-
- the existing template-parity assertion extended so the shipped fence contents
|
|
133
|
-
are pinned.
|
|
134
|
-
|
|
135
|
-
Mutation check: remove the deny branch and confirm the per-name cases fail.
|
|
136
|
-
Revert.
|
|
137
|
-
|
|
138
|
-
The reconcile suite and `fs-schema-guard-bash.test.sh` must both still pass
|
|
139
|
-
unchanged, which is the evidence that `allowed-top-level` was not disturbed.
|
|
140
|
-
|
|
141
|
-
## Out of scope
|
|
142
|
-
|
|
143
|
-
- The `declared-files` fence and the plugin-declaration mechanism. Task 2028
|
|
144
|
-
settled both.
|
|
145
|
-
- `.quarantine`, which is historical and already documented as never authored
|
|
146
|
-
into directly.
|
|
147
|
-
- `AGENTS.md`, which is in the allowed fence but outside the five names this task
|
|
148
|
-
names. The same overwritten-on-provision rationale may apply to it; that
|
|
149
|
-
decision is filed as
|
|
150
|
-
`.tasks/backlog/2057-agents-md-is-refreshed-every-provision-but-stays-agent-writable.md`.
|
package/payload/platform/docs/superpowers/specs/2026-07-28-task-2097-email-signature-set-design.md
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# Task 2097 — `email-signature-set` design
|
|
2
|
-
|
|
3
|
-
Date 2026-07-28. Lane *Platform / email · signature provisioning*.
|
|
4
|
-
Task file: [`.tasks/pending/2097-email-signature-setter-so-the-deterministic-loader-can-be-populated.md`](../../../../.tasks/pending/2097-email-signature-setter-so-the-deterministic-loader-can-be-populated.md).
|
|
5
|
-
|
|
6
|
-
## Problem
|
|
7
|
-
|
|
8
|
-
`email-draft` appends an account's signature deterministically, with no model judgement, at
|
|
9
|
-
[`compose.ts:113`](../../../plugins/email/mcp/src/lib/compose.ts). It calls `loadSignature(sigDir)` then
|
|
10
|
-
`applySignature`, where `loadSignature` (Task 1952,
|
|
11
|
-
[`signature.ts:24`](../../../plugins/email/mcp/src/lib/signature.ts)) reads three fixed-convention files at the
|
|
12
|
-
account root: `email-signature.txt`, `email-signature.html`, `email-signature.font`.
|
|
13
|
-
|
|
14
|
-
Nothing in the platform writes those files. Task 1952 shipped the reader with no writer, so the
|
|
15
|
-
path-and-format convention lives only in code and no account can be provisioned. On the G.L. Smith
|
|
16
|
-
account none of the three exist, `applied` was false, no signature was appended, and a drafting
|
|
17
|
-
subagent with no file-read tool reconstructed the HTML from a memory node and pulled a stale logo
|
|
18
|
-
into a client draft.
|
|
19
|
-
|
|
20
|
-
The failure is not a model ignoring a rule. The deterministic path works; it had no files to read.
|
|
21
|
-
|
|
22
|
-
## Outcome
|
|
23
|
-
|
|
24
|
-
An account's signature is installed into the loader's exact path and format through a sanctioned,
|
|
25
|
-
discoverable tool, and every subsequent draft carries it byte-for-byte with no model-authored
|
|
26
|
-
signature markup.
|
|
27
|
-
|
|
28
|
-
## Architecture
|
|
29
|
-
|
|
30
|
-
`lib/signature.ts` becomes the single owner of the signature convention in both directions. Read and
|
|
31
|
-
write cannot drift because one file defines the base path, the three extensions, and the font
|
|
32
|
-
trimming for both.
|
|
33
|
-
|
|
34
|
-
| Unit | Responsibility | Depends on |
|
|
35
|
-
|---|---|---|
|
|
36
|
-
| `lib/signature.ts` → `signatureDirFor(accountId)` | Resolve the one directory an account's signature lives in | `attachment-resolve.ts` → `accountDirFor`, `process.env.ACCOUNT_DIR` |
|
|
37
|
-
| `lib/signature.ts` → `saveSignature(accountDir, parts)` | Write present parts, remove absent ones, report both | `node:fs` |
|
|
38
|
-
| `lib/signature.ts` → `loadSignature` / `applySignature` | Unchanged (Task 1952) | — |
|
|
39
|
-
| `tools/email-signature-set.ts` | Param refusals, call `saveSignature`, log, format the response | `lib/signature.ts` |
|
|
40
|
-
| `lib/compose.ts` | Swaps its inline dir expression for `signatureDirFor` | `lib/signature.ts` |
|
|
41
|
-
|
|
42
|
-
### Directory resolution
|
|
43
|
-
|
|
44
|
-
`compose.ts:113` currently resolves the read directory inline as
|
|
45
|
-
`accountDirFor(accountId) || process.env.ACCOUNT_DIR`. The setter must write to the identical
|
|
46
|
-
directory in every environment, so that expression moves into `signatureDirFor(accountId)` and both
|
|
47
|
-
sides call it. Substituting the identical expression leaves `compose.ts` behaviour unchanged; the
|
|
48
|
-
point is that "which directory" now has one definition rather than two copies that can diverge.
|
|
49
|
-
|
|
50
|
-
Writing to `accountDirFor(accountId)` alone was rejected: on a spawn where `PLATFORM_ROOT` is absent
|
|
51
|
-
but `ACCOUNT_DIR` is set, the setter would refuse while drafts would have read the file fine.
|
|
52
|
-
|
|
53
|
-
## Behaviour
|
|
54
|
-
|
|
55
|
-
Parameters, all optional strings: `text`, `html`, `font`. The account is the caller's `accountId`,
|
|
56
|
-
the same authority `email-draft` uses. There is no `mailbox` parameter — a signature is account-scoped,
|
|
57
|
-
because `loadSignature` reads one account root and knows nothing about mailboxes.
|
|
58
|
-
|
|
59
|
-
| Input | Effect on `email-signature.<ext>` |
|
|
60
|
-
|---|---|
|
|
61
|
-
| `text` present | `.txt` written byte-for-byte verbatim, no reflow, no re-encoding |
|
|
62
|
-
| `html` present | `.html` written byte-for-byte verbatim |
|
|
63
|
-
| `font` present | `.font` written trimmed, matching `loadSignature`'s trim-on-read |
|
|
64
|
-
| any one absent | that file removed if it exists; absence of the file is not an error |
|
|
65
|
-
| all three absent | refused, nothing on disk is touched |
|
|
66
|
-
| directory unresolvable | refused, nothing on disk is touched |
|
|
67
|
-
|
|
68
|
-
Absent and empty are different inputs. A parameter is absent only when it is not supplied; a supplied
|
|
69
|
-
empty string is present, so it writes an empty file and counts toward the all-three-absent check. That
|
|
70
|
-
follows from writing verbatim: the tool stores what it was given rather than second-guessing it. The
|
|
71
|
-
consequence is that an empty `.txt` makes the next draft report `applied` true while appending
|
|
72
|
-
nothing visible, which is why an empty string is a deliberate act and omission is the way to remove.
|
|
73
|
-
|
|
74
|
-
The removal rule is what makes a text-only call clear a stale `.html` rather than leave it to keep
|
|
75
|
-
appending. The all-three-absent refusal is what stops a mis-shaped call silently wiping a working
|
|
76
|
-
signature; removal stays reachable, but only as a side effect of setting something.
|
|
77
|
-
|
|
78
|
-
The response names the resolved paths written and the resolved paths removed, so the caller sees the
|
|
79
|
-
account root it landed in rather than trusting it.
|
|
80
|
-
|
|
81
|
-
## Failure handling
|
|
82
|
-
|
|
83
|
-
Files are written in the fixed order `.txt`, `.html`, `.font`. A filesystem error surfaces verbatim
|
|
84
|
-
naming the offending path and the paths already written, so a half-applied state is reported rather
|
|
85
|
-
than hidden. There is no transactional rollback: the task does not ask for one, and reaching the
|
|
86
|
-
partial state requires a filesystem fault mid-call.
|
|
87
|
-
|
|
88
|
-
## Observability
|
|
89
|
-
|
|
90
|
-
`[email-signature-set] op=write accountId=<first-8> files=<written> removed=<removed>` on every
|
|
91
|
-
successful write. The task specifies the line through `files=`; `removed=` is added because the task
|
|
92
|
-
mandates removal as a behaviour and mandates that provisioning be auditable, and an unlogged removal
|
|
93
|
-
is not auditable.
|
|
94
|
-
|
|
95
|
-
The existing `[email-draft] op=signature applied=<bool> asset=<path>` line stays the per-draft
|
|
96
|
-
signal. An account that has run the setter shows `applied=true` on its next draft. That pair is the
|
|
97
|
-
end-to-end evidence: a write line naming the files, then a draft line reporting them applied.
|
|
98
|
-
|
|
99
|
-
## Registration
|
|
100
|
-
|
|
101
|
-
`ADMIN_CORE_TOOLS` is named in the task file but does not exist — `grep -rn 'ADMIN_CORE_TOOLS\s*[:=]'`
|
|
102
|
-
over `platform/` returns nothing, and the name survives only in comments. The live surfaces are:
|
|
103
|
-
|
|
104
|
-
1. `mcp/src/index.ts` — `eagerTool(server, name, description, zodShape, handler)`.
|
|
105
|
-
2. `PLUGIN.md` — a `tools:` entry (`riskClass: write_local`, `publicAllowlist: false`,
|
|
106
|
-
`adminAllowlist: false`, matching `email-setup`), a clause in the frontmatter `description:` prose,
|
|
107
|
-
an operator-docs bullet, and a skills-table row for the new skill.
|
|
108
|
-
3. `platform/templates/specialists/agents/personal-assistant.md` — the `tools:` frontmatter grant, the
|
|
109
|
-
canonical per-tool permission surface since Task 453.
|
|
110
|
-
|
|
111
|
-
`riskClass: write_local` is correct because the tool writes account-local files and sends nothing
|
|
112
|
-
outbound, which is the same class `email-setup` carries.
|
|
113
|
-
|
|
114
|
-
## Skill
|
|
115
|
-
|
|
116
|
-
`platform/plugins/email/skills/email-signature/SKILL.md`, activated when the operator asks to set or
|
|
117
|
-
change an email signature. It states the loader convention, and three standing rules:
|
|
118
|
-
|
|
119
|
-
- A signature is installed only through `email-signature-set`. Hand-writing the three files is not a
|
|
120
|
-
documented path.
|
|
121
|
-
- The agent never hand-builds a signature inside a draft body, because `email-draft` applies the
|
|
122
|
-
stored one automatically and a hand-built one both duplicates it and drifts from the asset.
|
|
123
|
-
- The signature content is read from the operator's own source of truth and passed through verbatim.
|
|
124
|
-
Reconstructing markup from memory or from stripped graph text is what put the stale logo in a
|
|
125
|
-
client draft.
|
|
126
|
-
|
|
127
|
-
Written as outcomes and constraints, not a step recipe, per `maxy-code/CLAUDE.md`.
|
|
128
|
-
|
|
129
|
-
## Testing
|
|
130
|
-
|
|
131
|
-
Added to the plugin's existing vitest surface at `mcp/src/__tests__/`, not substituted for it.
|
|
132
|
-
|
|
133
|
-
- Round trip: set all three, `loadSignature` returns the exact values, `applySignature` reports
|
|
134
|
-
`applied` true.
|
|
135
|
-
- Verbatim: text and html land byte-identical, trailing newlines and all; font lands trimmed.
|
|
136
|
-
- Text-only call writes `.txt` and removes a pre-existing `.html` and `.font`, so a later draft
|
|
137
|
-
carries no stale HTML.
|
|
138
|
-
- All-three-absent call is refused and leaves pre-existing files untouched.
|
|
139
|
-
- Unresolvable directory is refused.
|
|
140
|
-
- A draft composed after a successful set carries body and html exactly equal to `applySignature`
|
|
141
|
-
output, with no model-authored signature text.
|
|
142
|
-
- `signatureDirFor` prefers `accountDirFor` and falls back to `ACCOUNT_DIR`.
|
|
143
|
-
- A supplied empty string writes an empty file and does not count as absent.
|
|
144
|
-
- Existing `signature.test.ts` stays green.
|
|
145
|
-
|
|
146
|
-
## Out of scope
|
|
147
|
-
|
|
148
|
-
- Making `applied=false` a fail-closed refusal on client-facing drafts. Operator scoped this out on
|
|
149
|
-
2026-07-28 as separate hardening; deliberately not filed.
|
|
150
|
-
- The one-off G.L. Smith provisioning and the stale memory-node deletion. That is a runtime action on
|
|
151
|
-
the live install, performed through the tool this task ships.
|
|
152
|
-
- Any change to `loadSignature` / `applySignature` behaviour (Task 1952). `signatureDirFor` moves an
|
|
153
|
-
expression out of `compose.ts`; neither function's behaviour changes.
|
|
154
|
-
- The drafts list/verify capability, which is
|
|
155
|
-
[Task 2098](../../../../.tasks/pending/2098-email-drafts-list-verify-tool-so-draft-presence-is-confirmable.md).
|