@twinsearth/w2m-dsh-plugin 1.9.16 → 1.9.19

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 (40) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +21 -0
  4. package/README.md +3 -3
  5. package/bin/w2m-joint.mjs +36 -5
  6. package/bin/w2m-scrub.mjs +78 -0
  7. package/codex/mcp-servers.toml +1 -1
  8. package/docs/acceptance/1.9.17/RELEASE-1917.json +20 -0
  9. package/docs/acceptance/1.9.19/RELEASE-1919.json +21 -0
  10. package/docs/acceptance/2.0.0-beta.1/RELEASE-200B1.json +23 -0
  11. package/docs/acceptance-ops/2026-10-28/AG-SMOKE.json +24 -0
  12. package/docs/acceptance-ops/2026-10-28/G0-3-106.53.197.47.json +35 -0
  13. package/docs/acceptance-ops/2026-10-28/G0-3-202.182.123.154.json +35 -0
  14. package/docs/acceptance-ops/2026-10-28/G0-3-43.142.74.71.json +35 -0
  15. package/docs/acceptance-ops/2026-10-28/R1-OPERATOR-TOKEN.json +19 -0
  16. package/docs/acceptance-ops/2026-10-28/R3-CAPABILITIES.json +51 -0
  17. package/docs/acceptance-ops/2026-10-28/R8-LEDGER-JOURNAL.json +21 -0
  18. package/lib/client.js +474 -368
  19. package/package.json +4 -3
  20. package/plugin.json +1 -1
  21. package/src/agent/agent.mjs +6 -1
  22. package/src/agent/sinks/dir-sink.mjs +59 -13
  23. package/src/plugin/fleet-chat-route.mjs +7 -1
  24. package/src/plugin/joint-dev/audit-file.mjs +70 -37
  25. package/src/plugin/joint-dev/merge-engine.mjs +102 -0
  26. package/src/plugin/joint-dev/plan-store.mjs +117 -32
  27. package/src/plugin/joint-dev/relay-dispatch.mjs +171 -0
  28. package/src/plugin/operator-key.mjs +73 -0
  29. package/src/plugin/operator-token-route.mjs +71 -0
  30. package/src/plugin/tools-joint.mjs +97 -0
  31. package/src/plugin/tools.mjs +8 -3
  32. package/src/relay/fleet-messages.mjs +22 -0
  33. package/src/relay/offer-journal.mjs +71 -0
  34. package/src/relay/p0-projection.mjs +10 -0
  35. package/src/relay/report.mjs +40 -2
  36. package/src/relay/scrub.mjs +94 -81
  37. package/src/relay/server.mjs +2043 -2038
  38. package/src/relay/state.mjs +3351 -3300
  39. package/src/util/artifact-ref.mjs +88 -0
  40. package/src/util/redline-core.mjs +2 -1
@@ -0,0 +1,88 @@
1
+ /**
2
+ * ArtifactRef v1 (S4): the control-plane reference to an artifact that lives in a USER sink.
3
+ *
4
+ * Invariants:
5
+ * · `object_id` is random (32 hex), never derived from the content;
6
+ * · the control plane carries no plaintext hash — only `cipher.sha256` and `plain.bytes`;
7
+ * · a ref is metadata-only (≤ 4 KiB) and its fields are a closed set;
8
+ * · with a fleet key, `hmac` binds every field (HMAC-SHA-256 over canonical JSON).
9
+ */
10
+ import { createHash, createHmac, randomBytes } from 'node:crypto';
11
+
12
+ export const ARTIFACT_REF_VERSION = 1;
13
+ export const MAX_REF_BYTES = 4096;
14
+ export const ARTIFACT_KINDS = Object.freeze(['stdout', 'stderr', 'patch', 'commit', 'reduce-input']);
15
+ const HEX64 = /^[0-9a-f]{64}$/;
16
+ const HEX32 = /^[0-9a-f]{32}$/;
17
+
18
+ const stable = (value) => {
19
+ if (value === null || typeof value !== 'object') return JSON.stringify(value);
20
+ if (Array.isArray(value)) return '[' + value.map(stable).join(',') + ']';
21
+ return '{' + Object.keys(value).sort().map((key) => JSON.stringify(key) + ':' + stable(value[key])).join(',') + '}';
22
+ };
23
+
24
+ const digest = (value) => createHash('sha256').update(stable(value), 'utf8').digest('hex');
25
+
26
+ function hmacOf(ref, hmacKey) {
27
+ return createHmac('sha256', hmacKey).update(stable(ref), 'utf8').digest('base64url');
28
+ }
29
+
30
+ /**
31
+ * @param {{objectId?: string, planSha256?: string|null, approvalSha256?: string|null, taskId: string,
32
+ * stepId?: string|null, machineId: string, kind: string, sinkKind?: 'dir'|'lan-git'|'ssh',
33
+ * relPath: string, cipherSha256: string, cipherBytes: number, plainBytes: number,
34
+ * enc?: object, zip?: object, hmacKey?: string}} input
35
+ */
36
+ export function createArtifactRef(input) {
37
+ const objectId = typeof input?.objectId === 'string' && input.objectId !== '' ? input.objectId : randomBytes(16).toString('hex');
38
+ if (!HEX32.test(objectId)) return { ok: false, code: 'ARTIFACT_OBJECT_ID_INVALID' };
39
+ if (typeof input?.taskId !== 'string' || input.taskId === '') return { ok: false, code: 'ARTIFACT_TASK_REQUIRED' };
40
+ if (typeof input?.machineId !== 'string' || input.machineId === '') return { ok: false, code: 'ARTIFACT_MACHINE_REQUIRED' };
41
+ if (!ARTIFACT_KINDS.includes(input?.kind)) return { ok: false, code: 'ARTIFACT_KIND_INVALID' };
42
+ if (typeof input?.relPath !== 'string' || input.relPath === '' || input.relPath.includes('..')) return { ok: false, code: 'ARTIFACT_PATH_INVALID' };
43
+ if (!HEX64.test(String(input?.cipherSha256 ?? ''))) return { ok: false, code: 'ARTIFACT_CIPHER_SHA_INVALID' };
44
+ if (!Number.isInteger(input?.cipherBytes) || input.cipherBytes < 0) return { ok: false, code: 'ARTIFACT_CIPHER_BYTES_INVALID' };
45
+ if (!Number.isInteger(input?.plainBytes) || input.plainBytes < 0) return { ok: false, code: 'ARTIFACT_PLAIN_BYTES_INVALID' };
46
+ const ref = {
47
+ v: ARTIFACT_REF_VERSION,
48
+ object_id: objectId,
49
+ plan_sha256: HEX64.test(String(input?.planSha256 ?? '')) ? input.planSha256 : null,
50
+ approval_sha256: HEX64.test(String(input?.approvalSha256 ?? '')) ? input.approvalSha256 : null,
51
+ task_id: input.taskId,
52
+ step_id: typeof input?.stepId === 'string' && input.stepId !== '' ? input.stepId : null,
53
+ machine_id: input.machineId,
54
+ kind: input.kind,
55
+ sink: { kind: input?.sinkKind ?? 'dir', rel_path: input.relPath },
56
+ cipher: { sha256: input.cipherSha256, bytes: input.cipherBytes },
57
+ plain: { bytes: input.plainBytes },
58
+ enc: input?.enc ?? null,
59
+ zip: input?.zip ?? null,
60
+ created_at: new Date().toISOString(),
61
+ };
62
+ if (Buffer.byteLength(JSON.stringify(ref), 'utf8') > MAX_REF_BYTES) return { ok: false, code: 'ARTIFACT_REF_TOO_LARGE' };
63
+ if (typeof input?.hmacKey === 'string' && input.hmacKey !== '') ref.hmac = hmacOf(ref, input.hmacKey);
64
+ return { ok: true, ref };
65
+ }
66
+
67
+ /** Verify shape, size, the absence of a plaintext hash, and the HMAC when a key is supplied. */
68
+ export function verifyArtifactRef(ref, options = {}) {
69
+ if (ref === null || typeof ref !== 'object') return { ok: false, code: 'ARTIFACT_REF_INVALID' };
70
+ if (ref.v !== ARTIFACT_REF_VERSION) return { ok: false, code: 'ARTIFACT_VERSION_UNSUPPORTED' };
71
+ if (Buffer.byteLength(JSON.stringify(ref), 'utf8') > MAX_REF_BYTES) return { ok: false, code: 'ARTIFACT_REF_TOO_LARGE' };
72
+ if (!HEX32.test(String(ref.object_id ?? ''))) return { ok: false, code: 'ARTIFACT_OBJECT_ID_INVALID' };
73
+ if (!HEX64.test(String(ref?.cipher?.sha256 ?? ''))) return { ok: false, code: 'ARTIFACT_CIPHER_SHA_INVALID' };
74
+ if (typeof ref?.sink?.rel_path !== 'string' || ref.sink.rel_path.includes('..')) return { ok: false, code: 'ARTIFACT_PATH_INVALID' };
75
+ if (ref?.plain !== undefined && ref.plain !== null && typeof ref.plain === 'object' && 'sha256' in ref.plain) {
76
+ return { ok: false, code: 'ARTIFACT_PLAINTEXT_HASH_FORBIDDEN' };
77
+ }
78
+ for (const key of ['plain_sha256', 'sha256_plain', 'content_sha256']) {
79
+ if (key in ref) return { ok: false, code: 'ARTIFACT_PLAINTEXT_HASH_FORBIDDEN' };
80
+ }
81
+ if (typeof options.hmacKey === 'string' && options.hmacKey !== '') {
82
+ if (typeof ref.hmac !== 'string') return { ok: false, code: 'ARTIFACT_HMAC_MISSING' };
83
+ const { hmac: _ignored, ...body } = ref;
84
+ const expected = hmacOf(body, options.hmacKey);
85
+ if (expected !== ref.hmac) return { ok: false, code: 'ARTIFACT_HMAC_INVALID' };
86
+ }
87
+ return { ok: true, digest: digest(ref) };
88
+ }
@@ -24,7 +24,8 @@ export const BODY_ALLOWED_KEYS = Object.freeze([
24
24
  'allow_scope_overlap', 'include_offline', 'plan_sha256', 'step_ids',
25
25
  'manifest_sha256', 'executor_machine_id', 'head_commit', 'verifier_machine_id',
26
26
  'command_argv', 'command_hash', 'cwd_rel', 'timeout_ms',
27
- 'step_id', 'step_ids', 'machine_id', 'write_scope', 'depends_on',
27
+ 'step_id', 'step_ids', 'machine_id', 'write_scope', 'depends_on',
28
+ 'task_id', 'mode', 'approval_sha256', 'write',
28
29
  ]);
29
30
 
30
31
  /** 这些字段一律视为数据面内容,出现在控制消息里即拒绝(R1)。 */