peaks-loop 4.0.0-beta.14 → 4.0.0-beta.16
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/CHANGELOG.md +230 -148
- package/README-en.md +1 -1
- package/README.md +32 -1
- package/bin/peaks.js +0 -0
- package/dist/cli/cli-helpers.d.ts +1 -1
- package/dist/cli/cli-helpers.js +1 -1
- package/dist/cli/commands/adapter-commands-s2a.js +1 -1
- package/dist/cli/commands/asset-commands.js +33 -16
- package/dist/cli/commands/audit-commands.js +1 -1
- package/dist/cli/commands/bee-commands.js +1 -1
- package/dist/cli/commands/capability-commands.js +1 -1
- package/dist/cli/commands/classify-classify-commands.js +1 -1
- package/dist/cli/commands/code-commands.js +1 -1
- package/dist/cli/commands/code-review-commands.js +1 -1
- package/dist/cli/commands/codegraph-commands.js +1 -1
- package/dist/cli/commands/compact-command.js +1 -1
- package/dist/cli/commands/complexity-commands.js +1 -1
- package/dist/cli/commands/config-commands.js +1 -1
- package/dist/cli/commands/context-commands.js +1 -1
- package/dist/cli/commands/contract-commands.js +1 -1
- package/dist/cli/commands/core/artifacts-command.js +1 -1
- package/dist/cli/commands/core/binding-commands.js +1 -1
- package/dist/cli/commands/core/doctor-command.js +63 -3
- package/dist/cli/commands/core/memory-command.js +1 -1
- package/dist/cli/commands/core/profile-command.js +1 -1
- package/dist/cli/commands/core/proxy-command.js +1 -1
- package/dist/cli/commands/core/session-command.js +1 -1
- package/dist/cli/commands/core/skill-command.js +2 -2
- package/dist/cli/commands/core/standards-command.js +1 -1
- package/dist/cli/commands/dispatch-commands.js +2 -2
- package/dist/cli/commands/dispatch-from-dag.js +1 -1
- package/dist/cli/commands/doc-commands.js +1 -1
- package/dist/cli/commands/ecc-commands.js +2 -2
- package/dist/cli/commands/evolution-commands.js +1 -1
- package/dist/cli/commands/feedback-commands.js +1 -1
- package/dist/cli/commands/final-review-commands.js +1 -1
- package/dist/cli/commands/fixture-commands.js +1 -1
- package/dist/cli/commands/fork-commands.js +1 -1
- package/dist/cli/commands/gate-commands.js +1 -1
- package/dist/cli/commands/heartbeat-commands.js +1 -1
- package/dist/cli/commands/hook-handle.js +1 -1
- package/dist/cli/commands/hooks-commands.js +1 -1
- package/dist/cli/commands/ide-commands.js +1 -1
- package/dist/cli/commands/impact-commands.js +1 -1
- package/dist/cli/commands/job-commands.js +1 -1
- package/dist/cli/commands/legacy-commands.js +1 -1
- package/dist/cli/commands/log-commands.js +1 -1
- package/dist/cli/commands/loop-commands.js +1 -1
- package/dist/cli/commands/loop-eval-commands.js +1 -1
- package/dist/cli/commands/memory-commands.js +1 -1
- package/dist/cli/commands/migrate-1-4-1-command.js +1 -1
- package/dist/cli/commands/migrate-v2-10-to-v2-11-command.js +1 -1
- package/dist/cli/commands/mut-commands.d.ts +1 -1
- package/dist/cli/commands/mut-commands.js +1 -5
- package/dist/cli/commands/observability-commands.js +1 -1
- package/dist/cli/commands/openspec-commands.js +2 -2
- package/dist/cli/commands/perf-audit-commands.js +2 -2
- package/dist/cli/commands/perf-commands.js +1 -1
- package/dist/cli/commands/polyrepo-commands.js +1 -1
- package/dist/cli/commands/prd-blocks-commands.js +1 -1
- package/dist/cli/commands/prd-commands.js +1 -1
- package/dist/cli/commands/project-commands.js +1 -1
- package/dist/cli/commands/qa-business-review-commands.js +1 -1
- package/dist/cli/commands/qa-commands.d.ts +1 -1
- package/dist/cli/commands/qa-commands.js +2 -2
- package/dist/cli/commands/release-commands.js +1 -1
- package/dist/cli/commands/request-commands.js +1 -1
- package/dist/cli/commands/retrospective-commands.js +1 -1
- package/dist/cli/commands/reviewer-commands.js +1 -1
- package/dist/cli/commands/role-commands.js +1 -1
- package/dist/cli/commands/runtime-commands.js +1 -2
- package/dist/cli/commands/sc-commands.js +1 -1
- package/dist/cli/commands/scan-commands.js +1 -1
- package/dist/cli/commands/security-audit-commands.js +2 -2
- package/dist/cli/commands/share-commands.d.ts +19 -0
- package/dist/cli/commands/share-commands.js +121 -2
- package/dist/cli/commands/skill-conformance-commands.js +1 -1
- package/dist/cli/commands/skill-loop-engineering-readiness-commands.js +1 -1
- package/dist/cli/commands/skill-search-commands.js +1 -1
- package/dist/cli/commands/slice-commands.js +1 -1
- package/dist/cli/commands/slice-integrate-commands.js +1 -1
- package/dist/cli/commands/slice-review-commands.js +1 -1
- package/dist/cli/commands/smoke-commands.js +1 -1
- package/dist/cli/commands/sop-commands.js +1 -1
- package/dist/cli/commands/statusline-commands.js +1 -1
- package/dist/cli/commands/sub-agent-commands.js +2 -1
- package/dist/cli/commands/understand-commands.js +1 -1
- package/dist/cli/commands/upgrade-commands.js +1 -1
- package/dist/cli/commands/user-touchpoint-commands.js +1 -1
- package/dist/cli/commands/verdict-aggregate-command.js +1 -1
- package/dist/cli/commands/workflow-commands.js +1 -1
- package/dist/cli/commands/workflow-plan-commands.js +1 -1
- package/dist/cli/commands/workspace/init-command.js +1 -1
- package/dist/cli/commands/workspace/migrate-command.js +1 -1
- package/dist/cli/commands/workspace/reconcile-command.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/program.js +2 -2
- package/dist/services/artifacts/artifact-prerequisites.js +1 -1
- package/dist/services/artifacts/request-artifact-service.js +1 -1
- package/dist/services/artifacts/workspace-service.js +1 -1
- package/dist/services/code/auto-compact-orchestrator.js +5 -2
- package/dist/services/code-review/ocr-service.d.ts +1 -1
- package/dist/services/code-review/ocr-service.js +6 -3
- package/dist/services/config/config-types.js +1 -1
- package/dist/services/dashboard/project-dashboard-service.js +2 -2
- package/dist/services/dispatch/batch-counter.js +1 -1
- package/dist/services/dispatch/dispatch-record-writer.js +1 -1
- package/dist/services/job/job-progress-store.d.ts +2 -2
- package/dist/services/job/job-types.d.ts +27 -27
- package/dist/services/log/retention.js +1 -1
- package/dist/services/openspec/openspec-archive-service.js +1 -1
- package/dist/services/openspec/openspec-bridge-service.js +1 -1
- package/dist/services/openspec/openspec-init-service.js +1 -1
- package/dist/services/openspec/openspec-propose-from-doctor-service.js +1 -1
- package/dist/services/openspec/openspec-render-service.js +1 -1
- package/dist/services/openspec/openspec-scan-service.js +1 -1
- package/dist/services/openspec/openspec-validate-service.js +1 -1
- package/dist/services/scan/acceptance-coverage-service.js +1 -1
- package/dist/services/scan/archetype-service.js +1 -1
- package/dist/services/scan/existing-system-service.js +1 -1
- package/dist/services/scan/libraries-service.js +1 -1
- package/dist/services/skill/skill-search-service.d.ts +5 -5
- package/dist/services/skill/skill-search-service.js +2 -2
- package/dist/services/skills/skill-registry.js +2 -2
- package/dist/services/skills/skill-runbook-service.js +1 -1
- package/dist/services/slice/slice-check-service.js +1 -1
- package/dist/services/understand/understand-hybrid-service.js +1 -1
- package/dist/services/understand/understand-scan-service.js +2 -2
- package/dist/services/upgrade/upgrade-service.js +4 -3
- package/dist/services/verdict/envelopes.d.ts +2 -2
- package/dist/services/verdict/envelopes.js +2 -2
- package/dist/services/verdict/verdict-aggregator.d.ts +3 -3
- package/dist/services/workflow/autonomous-resume-writer.js +1 -1
- package/dist/services/workflow/plan-reader.d.ts +1 -1
- package/dist/services/workflow/plan-reader.js +1 -1
- package/dist/services/workflow/plan-refresher.d.ts +1 -1
- package/dist/services/workflow/plan-refresher.js +1 -1
- package/dist/services/workflow/plan-trigger-detector.d.ts +1 -1
- package/dist/services/workflow/plan-trigger-detector.js +1 -1
- package/dist/services/workflow/workflow-router-service.js +11 -9
- package/dist/services/workspace/migrate-service.js +1 -1
- package/dist/services/workspace/workspace-service.js +1 -1
- package/package.json +13 -8
- package/scripts/sync-version.mjs +8 -1
- package/skills/bee/peaks-rd/SKILL.md +14 -1
- package/skills/peaks-code/SKILL.md +44 -0
- package/skills/peaks-code/references/sub-agent-dispatch.md +30 -0
- package/dist/services/agent/ecc-cache-service.d.ts +0 -97
- package/dist/services/agent/ecc-cache-service.js +0 -491
- package/dist/services/audit-independent/perf-audit-service.d.ts +0 -151
- package/dist/services/audit-independent/perf-audit-service.js +0 -316
- package/dist/services/audit-independent/security-audit-service.d.ts +0 -155
- package/dist/services/audit-independent/security-audit-service.js +0 -316
- package/dist/services/context/dispatch-context-guard.d.ts +0 -27
- package/dist/services/context/dispatch-context-guard.js +0 -192
- package/dist/services/context/shared-channel.d.ts +0 -93
- package/dist/services/context/shared-channel.js +0 -308
- package/dist/services/crystallization/crystallization-service.d.ts +0 -239
- package/dist/services/crystallization/crystallization-service.js +0 -397
- package/dist/services/crystallization/crystallization-store.d.ts +0 -75
- package/dist/services/crystallization/crystallization-store.js +0 -193
- package/dist/services/crystallization/crystallization-types.d.ts +0 -313
- package/dist/services/crystallization/crystallization-types.js +0 -299
- package/dist/services/crystallization/evidence-brief-builder.d.ts +0 -237
- package/dist/services/crystallization/evidence-brief-builder.js +0 -240
- package/dist/services/doctor/doctor-service.d.ts +0 -149
- package/dist/services/doctor/doctor-service.js +0 -929
- package/dist/services/filesystem/file-lock.d.ts +0 -11
- package/dist/services/filesystem/file-lock.js +0 -152
- package/dist/services/final-review/final-review-service.d.ts +0 -12
- package/dist/services/final-review/final-review-service.js +0 -52
- package/dist/services/final-review/final-review-types.d.ts +0 -42
- package/dist/services/final-review/final-review-types.js +0 -18
- package/dist/services/job/job-resource-snapshot.d.ts +0 -7
- package/dist/services/job/job-resource-snapshot.js +0 -75
- package/dist/services/mut/assert-scanner.d.ts +0 -6
- package/dist/services/mut/assert-scanner.js +0 -116
- package/dist/services/mut/index.d.ts +0 -8
- package/dist/services/mut/index.js +0 -7
- package/dist/services/mut/mut-runner.d.ts +0 -39
- package/dist/services/mut/mut-runner.js +0 -29
- package/dist/services/mut/production-stryker.d.ts +0 -2
- package/dist/services/mut/production-stryker.js +0 -75
- package/dist/services/mut/report-builder.d.ts +0 -11
- package/dist/services/mut/report-builder.js +0 -110
- package/dist/services/mut/report-loader.d.ts +0 -31
- package/dist/services/mut/report-loader.js +0 -78
- package/dist/services/mut/thresholds.d.ts +0 -21
- package/dist/services/mut/thresholds.js +0 -22
- package/dist/services/mut/types.d.ts +0 -336
- package/dist/services/mut/types.js +0 -59
- package/dist/shared/fs.d.ts +0 -4
- package/dist/shared/fs.js +0 -26
- package/dist/shared/paths.d.ts +0 -6
- package/dist/shared/paths.js +0 -50
- package/dist/shared/result.d.ts +0 -22
- package/dist/shared/result.js +0 -46
- package/dist/shared/version.d.ts +0 -1
- package/dist/shared/version.js +0 -1
- package/examples/video-demo/README.md +0 -142
- package/examples/video-demo/out/en-closing-960.png +0 -0
- package/examples/video-demo/out/zh-closing-960.png +0 -0
- package/examples/video-demo/preview/peaks-loop-demo-en.mp4 +0 -0
- package/examples/video-demo/preview/peaks-loop-demo.mp4 +0 -0
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* peaks-security-audit service — independent security audit skill driver.
|
|
3
|
-
*
|
|
4
|
-
* Slice v2.12.0 (Group A, Tier 2). The skill is decoupled from the
|
|
5
|
-
* peaks-rd 5-way fan-out (per PRD AC-2.x) and lives as a standalone
|
|
6
|
-
* CLI surface: `peaks security-audit run --rid <id> ...`.
|
|
7
|
-
*
|
|
8
|
-
* The service owns:
|
|
9
|
-
* - 5-state detection of the security-audit runtime
|
|
10
|
-
* (handoff-missing / template-missing / dispatch-failed / template-malformed / ready)
|
|
11
|
-
* - Loading + sha256 verification of the prd/handoff.md
|
|
12
|
-
* - Loading the project-level security-template.md
|
|
13
|
-
* - Producing the audit envelope (verdict + violations) to write
|
|
14
|
-
* to `.peaks/_runtime/<sid>/audit/security-<rid>.md`
|
|
15
|
-
*
|
|
16
|
-
* The actual LLM-in-loop audit step is NOT in this service — the
|
|
17
|
-
* parent LLM (peaks-security-audit skill prompt) reads the template,
|
|
18
|
-
* runs the audit, and returns a structured envelope. This service
|
|
19
|
-
* is the pure I/O + validation core; the LLM is the judgement core.
|
|
20
|
-
*
|
|
21
|
-
* Why a service (not inlined in the CLI):
|
|
22
|
-
* - Unit-testable in isolation (6 case per PRD AC-2.8)
|
|
23
|
-
* - The 5-state detector mirrors the ecc-bridge detectEcc pattern
|
|
24
|
-
* (slice 7 Group D), keeping the surface uniform
|
|
25
|
-
* - Future migration to peaks sub-agent dispatch (peaks-rd main
|
|
26
|
-
* loop can opt to call this service directly without re-implementing
|
|
27
|
-
* the handoff verification)
|
|
28
|
-
*
|
|
29
|
-
* Cross-references:
|
|
30
|
-
* - PRD: `.peaks/_runtime/<sid>/prd/handoff.md` (sha256-locked, schemaVersion: 2)
|
|
31
|
-
* - Template: `.peaks/project-scan/security-template.md`
|
|
32
|
-
* - Output: `.peaks/_runtime/<sid>/audit/security-<rid>.md`
|
|
33
|
-
* - Schema: `.peaks/project-scan/audit-output-schema.md` (schemaVersion: 1)
|
|
34
|
-
*/
|
|
35
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
36
|
-
import { join, resolve, isAbsolute } from 'node:path';
|
|
37
|
-
import { createHash } from 'node:crypto';
|
|
38
|
-
/**
|
|
39
|
-
* Validate a raw value as a SecurityAuditEnvelope. Mirrors the
|
|
40
|
-
* `isEccEnvelope` strict-shape pattern.
|
|
41
|
-
*/
|
|
42
|
-
export function isSecurityAuditEnvelope(value) {
|
|
43
|
-
if (value === null || typeof value !== 'object')
|
|
44
|
-
return false;
|
|
45
|
-
const obj = value;
|
|
46
|
-
if (typeof obj.verdict !== 'string')
|
|
47
|
-
return false;
|
|
48
|
-
if (obj.verdict !== 'pass' && obj.verdict !== 'warn' && obj.verdict !== 'block') {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
if (!Array.isArray(obj.violations))
|
|
52
|
-
return false;
|
|
53
|
-
if (typeof obj.summary !== 'string')
|
|
54
|
-
return false;
|
|
55
|
-
for (const v of obj.violations) {
|
|
56
|
-
if (v === null || typeof v !== 'object')
|
|
57
|
-
return false;
|
|
58
|
-
const vo = v;
|
|
59
|
-
if (typeof vo.dimension !== 'string')
|
|
60
|
-
return false;
|
|
61
|
-
if (typeof vo.severity !== 'string')
|
|
62
|
-
return false;
|
|
63
|
-
if (vo.severity !== 'CRITICAL' && vo.severity !== 'HIGH' && vo.severity !== 'MED' && vo.severity !== 'LOW') {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
if (typeof vo.file !== 'string')
|
|
67
|
-
return false;
|
|
68
|
-
if (typeof vo.line !== 'number' || !Number.isFinite(vo.line))
|
|
69
|
-
return false;
|
|
70
|
-
if (typeof vo.hint !== 'string')
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Read the peaks-prd handoff frontmatter + body, verify sha256 matches
|
|
77
|
-
* the body. Returns null on missing file or sha256 mismatch; otherwise
|
|
78
|
-
* returns the parsed frontmatter + body string.
|
|
79
|
-
*
|
|
80
|
-
* Defensive: we read frontmatter via a 1-line regex match, not via
|
|
81
|
-
* a YAML parser. The handoff service (`src/services/prd/handoff-service.ts`)
|
|
82
|
-
* is the canonical writer; we only consume its output here.
|
|
83
|
-
*/
|
|
84
|
-
export function readAndVerifyHandoff(handoffPath, projectRoot) {
|
|
85
|
-
if (!handoffPath || !isAbsolute(handoffPath)) {
|
|
86
|
-
const absolutePath = resolve(projectRoot, handoffPath);
|
|
87
|
-
if (!existsSync(absolutePath))
|
|
88
|
-
return null;
|
|
89
|
-
return readAndVerifyHandoff(absolutePath, projectRoot);
|
|
90
|
-
}
|
|
91
|
-
if (!existsSync(handoffPath))
|
|
92
|
-
return null;
|
|
93
|
-
let raw;
|
|
94
|
-
try {
|
|
95
|
-
raw = readFileSync(handoffPath, 'utf8');
|
|
96
|
-
}
|
|
97
|
-
catch { // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
// Frontmatter pattern: `---` line, then key: value pairs, then `---` line.
|
|
101
|
-
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
102
|
-
if (match === null)
|
|
103
|
-
return null;
|
|
104
|
-
const frontmatterRaw = match[1];
|
|
105
|
-
const body = match[2];
|
|
106
|
-
const sha256Match = frontmatterRaw.match(/^sha256:\s*([a-f0-9]{64})\s*$/m);
|
|
107
|
-
const schemaVersionMatch = frontmatterRaw.match(/^schemaVersion:\s*(\d+)\s*$/m);
|
|
108
|
-
if (sha256Match === null || schemaVersionMatch === null)
|
|
109
|
-
return null;
|
|
110
|
-
const sha256Expected = sha256Match[1];
|
|
111
|
-
const schemaVersion = parseInt(schemaVersionMatch[1], 10);
|
|
112
|
-
const sha256Actual = createHash('sha256').update(body, 'utf8').digest('hex');
|
|
113
|
-
if (sha256Expected !== sha256Actual)
|
|
114
|
-
return null;
|
|
115
|
-
return {
|
|
116
|
-
frontmatter: { sha256: sha256Expected, schemaVersion },
|
|
117
|
-
body,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Read the project-level security-template.md. Returns the body
|
|
122
|
-
* string (with frontmatter stripped) or null if the file is absent.
|
|
123
|
-
* Does NOT validate the template content; the parent LLM validates
|
|
124
|
-
* by reading.
|
|
125
|
-
*/
|
|
126
|
-
export function readSecurityTemplate(projectRoot) {
|
|
127
|
-
const templatePath = join(projectRoot, '.peaks', 'project-scan', 'security-template.md');
|
|
128
|
-
if (!existsSync(templatePath))
|
|
129
|
-
return null;
|
|
130
|
-
try {
|
|
131
|
-
return readFileSync(templatePath, 'utf8');
|
|
132
|
-
}
|
|
133
|
-
catch { // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* 5-state detector. The parent skill inspects the state and either
|
|
139
|
-
* proceeds (when `ready`) or records a degradation note and falls
|
|
140
|
-
* back (when any other state).
|
|
141
|
-
*
|
|
142
|
-
* Soft-fail policy mirrors `detectEcc` (slice 7 Group D): the CLI
|
|
143
|
-
* never throws; it returns a typed detect result.
|
|
144
|
-
*/
|
|
145
|
-
export function detectSecurityAudit(input) {
|
|
146
|
-
const warnings = [];
|
|
147
|
-
const nextActions = [];
|
|
148
|
-
const handoffPath = join(input.projectRoot, '.peaks', '_runtime', input.sessionId, 'prd', 'handoff.md');
|
|
149
|
-
const handoffPresent = existsSync(handoffPath);
|
|
150
|
-
const templatePath = join(input.projectRoot, '.peaks', 'project-scan', 'security-template.md');
|
|
151
|
-
const templatePresent = existsSync(templatePath);
|
|
152
|
-
if (!handoffPresent) {
|
|
153
|
-
return {
|
|
154
|
-
state: 'handoff-missing',
|
|
155
|
-
handoffPresent: false,
|
|
156
|
-
templatePresent,
|
|
157
|
-
warnings: [`peaks-prd handoff not found at ${handoffPath}`],
|
|
158
|
-
nextActions: [
|
|
159
|
-
'Run peaks-prd handoff init to produce a sha256-locked handoff before running peaks security-audit.',
|
|
160
|
-
'Until the handoff exists, peaks-security-audit cannot start (gate fail).'
|
|
161
|
-
]
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
if (!templatePresent) {
|
|
165
|
-
return {
|
|
166
|
-
state: 'template-missing',
|
|
167
|
-
handoffPresent: true,
|
|
168
|
-
templatePresent: false,
|
|
169
|
-
warnings: [`security-template.md not found at ${templatePath}`],
|
|
170
|
-
nextActions: [
|
|
171
|
-
'Run peaks project template init to bootstrap the 3 audit templates (security-template, perf-template, audit-output-schema).',
|
|
172
|
-
'Until the template exists, peaks-security-audit cannot start (gate fail).'
|
|
173
|
-
]
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
if (input.dispatchError !== undefined) {
|
|
177
|
-
return {
|
|
178
|
-
state: 'dispatch-failed',
|
|
179
|
-
handoffPresent: true,
|
|
180
|
-
templatePresent: true,
|
|
181
|
-
warnings: [
|
|
182
|
-
`Audit dispatch failed: ${input.dispatchError instanceof Error ? input.dispatchError.message : String(input.dispatchError)}`
|
|
183
|
-
],
|
|
184
|
-
nextActions: [
|
|
185
|
-
'Inspect the audit prompt template for the failure cause.',
|
|
186
|
-
'peaks-security-audit falls back to inline LLM review (degradation note: security-audit-dispatch-failed).'
|
|
187
|
-
]
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
if (input.envelope !== undefined && !isSecurityAuditEnvelope(input.envelope)) {
|
|
191
|
-
return {
|
|
192
|
-
state: 'envelope-malformed',
|
|
193
|
-
handoffPresent: true,
|
|
194
|
-
templatePresent: true,
|
|
195
|
-
warnings: [
|
|
196
|
-
'Security-audit returned a value that failed envelope validation (expected { verdict, violations, summary }).'
|
|
197
|
-
],
|
|
198
|
-
nextActions: [
|
|
199
|
-
'Verify the security-audit skill prompt matches PRD AC-2.x output shape.',
|
|
200
|
-
'If the envelope shape is intentionally different, file a bug at the peaks-loop repo.'
|
|
201
|
-
]
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
return {
|
|
205
|
-
state: 'ready',
|
|
206
|
-
handoffPresent: true,
|
|
207
|
-
templatePresent: true,
|
|
208
|
-
warnings,
|
|
209
|
-
nextActions
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Render a SecurityAuditEnvelope to the canonical audit artifact body
|
|
214
|
-
* (markdown). Mirrors `adaptEccEnvelopeToRdCodeReview` in ecc-bridge.ts:
|
|
215
|
-
* produce a markdown body with all required sections, ready to write
|
|
216
|
-
* to `.peaks/_runtime/<sid>/audit/security-<rid>.md`.
|
|
217
|
-
*/
|
|
218
|
-
export function renderSecurityAuditArtifact(env, opts) {
|
|
219
|
-
const counts = {};
|
|
220
|
-
for (const v of env.violations) {
|
|
221
|
-
counts[v.severity] = (counts[v.severity] ?? 0) + 1;
|
|
222
|
-
}
|
|
223
|
-
const criticalCount = counts['CRITICAL'] ?? 0;
|
|
224
|
-
const findingsBullets = env.violations.length === 0
|
|
225
|
-
? '- (none)'
|
|
226
|
-
: env.violations.map((v) => `- [${v.severity}] ${v.dimension} @ ${v.file}:${v.line} — ${v.hint}`).join('\n');
|
|
227
|
-
const requiredFixes = env.violations.length === 0
|
|
228
|
-
? ''
|
|
229
|
-
: [
|
|
230
|
-
'## Required fixes',
|
|
231
|
-
'',
|
|
232
|
-
...env.violations.map((v) => `- [${v.severity}] ${v.file}:${v.line} — ${v.hint}`),
|
|
233
|
-
''
|
|
234
|
-
].join('\n');
|
|
235
|
-
const body = [
|
|
236
|
-
'## Summary',
|
|
237
|
-
'',
|
|
238
|
-
env.summary,
|
|
239
|
-
'',
|
|
240
|
-
'## Threat model coverage',
|
|
241
|
-
'',
|
|
242
|
-
`audited at: ${opts.generatedAt}`,
|
|
243
|
-
`verdict: ${env.verdict}`,
|
|
244
|
-
`handoffHash: ${opts.handoffHash}`,
|
|
245
|
-
'',
|
|
246
|
-
'## Findings',
|
|
247
|
-
'',
|
|
248
|
-
findingsBullets,
|
|
249
|
-
'',
|
|
250
|
-
requiredFixes,
|
|
251
|
-
'## Verdict',
|
|
252
|
-
'',
|
|
253
|
-
`verdict: ${env.verdict}`,
|
|
254
|
-
`CRITICAL: ${criticalCount}`,
|
|
255
|
-
''
|
|
256
|
-
].filter((s) => s.length > 0).join('\n');
|
|
257
|
-
return {
|
|
258
|
-
body,
|
|
259
|
-
violationsCount: env.violations.length,
|
|
260
|
-
verdict: env.verdict,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Write the rendered audit artifact to
|
|
265
|
-
* `.peaks/_runtime/<sid>/audit/security-<rid>.md`.
|
|
266
|
-
*
|
|
267
|
-
* Atomic write (tmp + rename) to avoid partial writes on crash.
|
|
268
|
-
* Returns the absolute path on success.
|
|
269
|
-
*/
|
|
270
|
-
export function writeSecurityAuditArtifact(projectRoot, sessionId, rid, body) {
|
|
271
|
-
const targetDir = join(projectRoot, '.peaks', '_runtime', sessionId, 'audit');
|
|
272
|
-
mkdirSync(targetDir, { recursive: true });
|
|
273
|
-
const targetPath = join(targetDir, `security-${rid}.md`);
|
|
274
|
-
// tmp + rename for atomicity
|
|
275
|
-
const tmpPath = `${targetPath}.tmp`;
|
|
276
|
-
writeFileSync(tmpPath, body, 'utf8');
|
|
277
|
-
// Sync rename: on Windows, fs.renameSync overwrites if target exists (Node 22+)
|
|
278
|
-
const { renameSync } = require('node:fs');
|
|
279
|
-
renameSync(tmpPath, targetPath);
|
|
280
|
-
return targetPath;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Convenience: run `detectSecurityAudit` + `renderSecurityAuditArtifact`
|
|
284
|
-
* + `writeSecurityAuditArtifact` in one call.
|
|
285
|
-
*
|
|
286
|
-
* Returns `{ detect, artifactPath, violationsCount, verdict }` or
|
|
287
|
-
* `{ detect, artifactPath: null, ... }` when not ready.
|
|
288
|
-
*/
|
|
289
|
-
export function runSecurityAudit(input) {
|
|
290
|
-
const detect = detectSecurityAudit({
|
|
291
|
-
projectRoot: input.projectRoot,
|
|
292
|
-
sessionId: input.sessionId,
|
|
293
|
-
...(input.dispatchError !== undefined ? { dispatchError: input.dispatchError } : {}),
|
|
294
|
-
...(input.envelope !== undefined ? { envelope: input.envelope } : {})
|
|
295
|
-
});
|
|
296
|
-
if (detect.state !== 'ready' || input.envelope === undefined) {
|
|
297
|
-
return { detect, artifactPath: null, violationsCount: 0, verdict: null };
|
|
298
|
-
}
|
|
299
|
-
// detect.state === 'ready' implies input.envelope passed isSecurityAuditEnvelope.
|
|
300
|
-
const env = input.envelope;
|
|
301
|
-
const handoffPath = join(input.projectRoot, '.peaks', '_runtime', input.sessionId, 'prd', 'handoff.md');
|
|
302
|
-
const verified = readAndVerifyHandoff(handoffPath, input.projectRoot);
|
|
303
|
-
const handoffHash = verified?.frontmatter.sha256 ?? 'unknown';
|
|
304
|
-
const rendered = renderSecurityAuditArtifact(env, {
|
|
305
|
-
rid: input.rid,
|
|
306
|
-
handoffHash,
|
|
307
|
-
generatedAt: input.generatedAt
|
|
308
|
-
});
|
|
309
|
-
const artifactPath = writeSecurityAuditArtifact(input.projectRoot, input.sessionId, input.rid, rendered.body);
|
|
310
|
-
return {
|
|
311
|
-
detect,
|
|
312
|
-
artifactPath,
|
|
313
|
-
violationsCount: rendered.violationsCount,
|
|
314
|
-
verdict: rendered.verdict
|
|
315
|
-
};
|
|
316
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/** Build the canonical artifact path for a given session/rid/role/idx/ext. */
|
|
2
|
-
export declare function artifactPath(projectRoot: string, sid: string, rid: string, role: string, idx: number, ext?: string): string;
|
|
3
|
-
/** Build the canonical shared channel path. */
|
|
4
|
-
export declare function sharedChannelPath(projectRoot: string, sid: string, rid: string, batchId: string): string;
|
|
5
|
-
/**
|
|
6
|
-
* Assert that `artifactPath` lives under
|
|
7
|
-
* `projectRoot/.peaks/_sub_agents/<sid>/artifacts/`. Rejects
|
|
8
|
-
* symlink/junction escapes and `..` segments.
|
|
9
|
-
*
|
|
10
|
-
* Throws an Error with `.code = 'INVALID_ARTIFACT_PATH'` on rejection.
|
|
11
|
-
*/
|
|
12
|
-
export declare function assertSafeArtifactPath(artifactPathInput: string, projectRoot: string): string;
|
|
13
|
-
/**
|
|
14
|
-
* Assert that `channelPath` lives under
|
|
15
|
-
* `projectRoot/.peaks/_sub_agents/<sid>/shared/`. Same R-2 logic as
|
|
16
|
-
* `assertSafeArtifactPath` but with a different canonical subdir.
|
|
17
|
-
*
|
|
18
|
-
* Throws an Error with `.code = 'INVALID_SHARED_CHANNEL_PATH'` on rejection.
|
|
19
|
-
*/
|
|
20
|
-
export declare function assertSafeSharedChannelPath(channelPathInput: string, projectRoot: string): string;
|
|
21
|
-
/**
|
|
22
|
-
* Soft-warn check on the artifact file name pattern. Returns null if
|
|
23
|
-
* the name matches `<rid>-<role>-<idx>.<ext>`, otherwise returns a
|
|
24
|
-
* warning string. Does NOT reject (the path is still in the canonical
|
|
25
|
-
* dir; the warning is for human/audit readability per G7.4.c).
|
|
26
|
-
*/
|
|
27
|
-
export declare function checkArtifactNameConvention(artifactPathInput: string): string | null;
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* G7.4 path safety + G8 path safety — R-2 symlink/junction guard for
|
|
3
|
-
* sub-agent artifact + shared channel paths.
|
|
4
|
-
*
|
|
5
|
-
* Slice #009's `assertSafeDispatchRecordPath` covers the dispatch record
|
|
6
|
-
* path. This module covers two adjacent paths under the same canonical
|
|
7
|
-
* root (`.peaks/_sub_agents/<sid>/`):
|
|
8
|
-
*
|
|
9
|
-
* - `artifacts/<rid>-<role>-<idx>.<ext>` (G7 write-artifact)
|
|
10
|
-
* - `shared/<batchId>.json` (G8 share / shared-read)
|
|
11
|
-
*
|
|
12
|
-
* Both reuse the same R-2 logic: reject `..` segments BEFORE the OS
|
|
13
|
-
* resolver collapses them (POSIX normalize silently drops `..`), reject
|
|
14
|
-
* absolute paths that escape the canonical root, resolve symlinks via
|
|
15
|
-
* `realpathSync` and re-check the resolved path.
|
|
16
|
-
*
|
|
17
|
-
* The canonical-root pattern is intentionally identical to the slice #009
|
|
18
|
-
* helper, so future audits can reuse one mental model: "all sub-agent
|
|
19
|
-
* state files must live under `.peaks/_sub_agents/<sid>/` and pass the
|
|
20
|
-
* same R-2 guard."
|
|
21
|
-
*/
|
|
22
|
-
import { realpathSync } from 'node:fs';
|
|
23
|
-
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
24
|
-
const SUB_AGENTS_DIR = '_sub_agents';
|
|
25
|
-
const ARTIFACTS_SUBDIR = 'artifacts';
|
|
26
|
-
const SHARED_SUBDIR = 'shared';
|
|
27
|
-
const ARTIFACT_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.[A-Za-z0-9]+$/;
|
|
28
|
-
const BATCH_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
29
|
-
/** Build the canonical artifact path for a given session/rid/role/idx/ext. */
|
|
30
|
-
export function artifactPath(projectRoot, sid, rid, role, idx, ext = 'md') {
|
|
31
|
-
if (typeof role !== 'string' || role.length === 0) {
|
|
32
|
-
throw new Error('artifactPath: role must be non-empty');
|
|
33
|
-
}
|
|
34
|
-
if (!Number.isInteger(idx) || idx < 1) {
|
|
35
|
-
throw new Error(`artifactPath: idx must be positive integer (got ${idx})`);
|
|
36
|
-
}
|
|
37
|
-
const safeSid = sanitizeSegment(sid, 'sessionId');
|
|
38
|
-
const safeRid = sanitizeSegment(rid, 'requestId');
|
|
39
|
-
const safeRole = sanitizeSegment(role, 'role');
|
|
40
|
-
const safeExt = ext.replace(/[^A-Za-z0-9]/g, '');
|
|
41
|
-
if (safeExt.length === 0) {
|
|
42
|
-
throw new Error(`artifactPath: ext must be alphanumeric (got "${ext}")`);
|
|
43
|
-
}
|
|
44
|
-
return resolve(projectRoot, '.peaks', SUB_AGENTS_DIR, safeSid, ARTIFACTS_SUBDIR, `${safeRid}-${safeRole}-${String(idx).padStart(3, '0')}.${safeExt}`);
|
|
45
|
-
}
|
|
46
|
-
/** Build the canonical shared channel path. */
|
|
47
|
-
export function sharedChannelPath(projectRoot, sid, rid, batchId) {
|
|
48
|
-
if (typeof batchId !== 'string' || batchId.length === 0) {
|
|
49
|
-
throw new Error('sharedChannelPath: batchId must be non-empty');
|
|
50
|
-
}
|
|
51
|
-
const safeSid = sanitizeSegment(sid, 'sessionId');
|
|
52
|
-
const safeRid = sanitizeSegment(rid, 'requestId');
|
|
53
|
-
if (!BATCH_ID_PATTERN.test(batchId)) {
|
|
54
|
-
throw new Error(`sharedChannelPath: batchId must match ${BATCH_ID_PATTERN} (got "${batchId}")`);
|
|
55
|
-
}
|
|
56
|
-
return resolve(projectRoot, '.peaks', SUB_AGENTS_DIR, safeSid, SHARED_SUBDIR, `${safeRid}-${batchId}.json`);
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Assert that `artifactPath` lives under
|
|
60
|
-
* `projectRoot/.peaks/_sub_agents/<sid>/artifacts/`. Rejects
|
|
61
|
-
* symlink/junction escapes and `..` segments.
|
|
62
|
-
*
|
|
63
|
-
* Throws an Error with `.code = 'INVALID_ARTIFACT_PATH'` on rejection.
|
|
64
|
-
*/
|
|
65
|
-
export function assertSafeArtifactPath(artifactPathInput, projectRoot) {
|
|
66
|
-
if (!isAbsolute(artifactPathInput)) {
|
|
67
|
-
throw invalidPathError(artifactPathInput, 'must be absolute');
|
|
68
|
-
}
|
|
69
|
-
const rawSegments = artifactPathInput.split(/[\\/]/);
|
|
70
|
-
if (rawSegments.includes('..')) {
|
|
71
|
-
throw invalidPathError(artifactPathInput, 'must not contain .. segments');
|
|
72
|
-
}
|
|
73
|
-
const expected = resolve(projectRoot, '.peaks', SUB_AGENTS_DIR);
|
|
74
|
-
const rel = relative(expected, artifactPathInput);
|
|
75
|
-
if (rel.startsWith('..') || isAbsolute(rel)) {
|
|
76
|
-
throw invalidPathError(artifactPathInput, 'must be under .peaks/_sub_agents/');
|
|
77
|
-
}
|
|
78
|
-
let realArtifact;
|
|
79
|
-
let realRoot;
|
|
80
|
-
try {
|
|
81
|
-
const parent = dirname(artifactPathInput);
|
|
82
|
-
const realParent = realpathSync(parent);
|
|
83
|
-
realArtifact = resolve(realParent, artifactPathInput.slice(parent.length + 1));
|
|
84
|
-
realRoot = realpathSync(projectRoot);
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
const fallback = resolve(projectRoot, '.peaks', SUB_AGENTS_DIR);
|
|
88
|
-
const rel2 = relative(fallback, artifactPathInput);
|
|
89
|
-
if (rel2.startsWith('..') || isAbsolute(rel2)) {
|
|
90
|
-
throw invalidPathError(artifactPathInput, 'must be under .peaks/_sub_agents/');
|
|
91
|
-
}
|
|
92
|
-
return artifactPathInput;
|
|
93
|
-
}
|
|
94
|
-
const realRel = relative(realRoot, realArtifact);
|
|
95
|
-
if (realRel.startsWith('..') || isAbsolute(realRel)) {
|
|
96
|
-
throw invalidPathError(artifactPathInput, 'symlink/junction escapes project root');
|
|
97
|
-
}
|
|
98
|
-
const realExpected = resolve(realRoot, '.peaks', SUB_AGENTS_DIR);
|
|
99
|
-
const realRelExpected = relative(realExpected, realArtifact);
|
|
100
|
-
if (realRelExpected.startsWith('..') || isAbsolute(realRelExpected)) {
|
|
101
|
-
throw invalidPathError(artifactPathInput, 'must be under .peaks/_sub_agents/');
|
|
102
|
-
}
|
|
103
|
-
return realArtifact;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Assert that `channelPath` lives under
|
|
107
|
-
* `projectRoot/.peaks/_sub_agents/<sid>/shared/`. Same R-2 logic as
|
|
108
|
-
* `assertSafeArtifactPath` but with a different canonical subdir.
|
|
109
|
-
*
|
|
110
|
-
* Throws an Error with `.code = 'INVALID_SHARED_CHANNEL_PATH'` on rejection.
|
|
111
|
-
*/
|
|
112
|
-
export function assertSafeSharedChannelPath(channelPathInput, projectRoot) {
|
|
113
|
-
if (!isAbsolute(channelPathInput)) {
|
|
114
|
-
throw invalidPathErrorShared(channelPathInput, 'must be absolute');
|
|
115
|
-
}
|
|
116
|
-
const rawSegments = channelPathInput.split(/[\\/]/);
|
|
117
|
-
if (rawSegments.includes('..')) {
|
|
118
|
-
throw invalidPathErrorShared(channelPathInput, 'must not contain .. segments');
|
|
119
|
-
}
|
|
120
|
-
const expected = resolve(projectRoot, '.peaks', SUB_AGENTS_DIR);
|
|
121
|
-
const rel = relative(expected, channelPathInput);
|
|
122
|
-
if (rel.startsWith('..') || isAbsolute(rel)) {
|
|
123
|
-
throw invalidPathErrorShared(channelPathInput, 'must be under .peaks/_sub_agents/shared/');
|
|
124
|
-
}
|
|
125
|
-
let realChannel;
|
|
126
|
-
let realRoot;
|
|
127
|
-
try {
|
|
128
|
-
const parent = dirname(channelPathInput);
|
|
129
|
-
const realParent = realpathSync(parent);
|
|
130
|
-
realChannel = resolve(realParent, channelPathInput.slice(parent.length + 1));
|
|
131
|
-
realRoot = realpathSync(projectRoot);
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
const fallback = resolve(projectRoot, '.peaks', SUB_AGENTS_DIR);
|
|
135
|
-
const rel2 = relative(fallback, channelPathInput);
|
|
136
|
-
if (rel2.startsWith('..') || isAbsolute(rel2)) {
|
|
137
|
-
throw invalidPathErrorShared(channelPathInput, 'must be under .peaks/_sub_agents/shared/');
|
|
138
|
-
}
|
|
139
|
-
return channelPathInput;
|
|
140
|
-
}
|
|
141
|
-
const realRel = relative(realRoot, realChannel);
|
|
142
|
-
if (realRel.startsWith('..') || isAbsolute(realRel)) {
|
|
143
|
-
throw invalidPathErrorShared(channelPathInput, 'symlink/junction escapes project root');
|
|
144
|
-
}
|
|
145
|
-
const realExpected = resolve(realRoot, '.peaks', SUB_AGENTS_DIR);
|
|
146
|
-
const realRelExpected = relative(realExpected, realChannel);
|
|
147
|
-
if (realRelExpected.startsWith('..') || isAbsolute(realRelExpected)) {
|
|
148
|
-
throw invalidPathErrorShared(channelPathInput, 'must be under .peaks/_sub_agents/shared/');
|
|
149
|
-
}
|
|
150
|
-
return realChannel;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Soft-warn check on the artifact file name pattern. Returns null if
|
|
154
|
-
* the name matches `<rid>-<role>-<idx>.<ext>`, otherwise returns a
|
|
155
|
-
* warning string. Does NOT reject (the path is still in the canonical
|
|
156
|
-
* dir; the warning is for human/audit readability per G7.4.c).
|
|
157
|
-
*/
|
|
158
|
-
export function checkArtifactNameConvention(artifactPathInput) {
|
|
159
|
-
const base = artifactPathInput.split(/[\\/]/).pop() ?? '';
|
|
160
|
-
if (ARTIFACT_NAME_PATTERN.test(base)) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
return `Artifact file name does not match <rid>-<role>-<idx>.<ext> convention: ${base}`;
|
|
164
|
-
}
|
|
165
|
-
function sanitizeSegment(value, field) {
|
|
166
|
-
if (typeof value !== 'string' || value.length === 0) {
|
|
167
|
-
throw new Error(`${field} must be non-empty`);
|
|
168
|
-
}
|
|
169
|
-
if (value.length > 256) {
|
|
170
|
-
throw new Error(`${field} must be ≤ 256 chars (got ${value.length})`);
|
|
171
|
-
}
|
|
172
|
-
for (let i = 0; i < value.length; i += 1) {
|
|
173
|
-
const code = value.charCodeAt(i);
|
|
174
|
-
if (code <= 0x20 || code === 0x7F) {
|
|
175
|
-
throw new Error(`${field} must not contain whitespace or control characters`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (value.includes('..') || value.includes('/') || value.includes('\\')) {
|
|
179
|
-
throw new Error(`${field} must not contain '..' or path separators`);
|
|
180
|
-
}
|
|
181
|
-
return value;
|
|
182
|
-
}
|
|
183
|
-
function invalidPathError(path, reason) {
|
|
184
|
-
const e = new Error(`Invalid artifact path: ${reason} (path: ${path})`);
|
|
185
|
-
e.code = 'INVALID_ARTIFACT_PATH';
|
|
186
|
-
return e;
|
|
187
|
-
}
|
|
188
|
-
function invalidPathErrorShared(path, reason) {
|
|
189
|
-
const e = new Error(`Invalid shared channel path: ${reason} (path: ${path})`);
|
|
190
|
-
e.code = 'INVALID_SHARED_CHANNEL_PATH';
|
|
191
|
-
return e;
|
|
192
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
export interface SharedChannelEntry {
|
|
2
|
-
readonly version: 1;
|
|
3
|
-
readonly at: string;
|
|
4
|
-
readonly from: string;
|
|
5
|
-
readonly key: string;
|
|
6
|
-
readonly value: Readonly<Record<string, unknown>>;
|
|
7
|
-
readonly valueSize: number;
|
|
8
|
-
}
|
|
9
|
-
export interface SharedChannel {
|
|
10
|
-
readonly batchId: string;
|
|
11
|
-
readonly createdAt: string;
|
|
12
|
-
readonly updatedAt: string;
|
|
13
|
-
readonly entries: Readonly<Record<string, SharedChannelEntry>>;
|
|
14
|
-
}
|
|
15
|
-
export declare const SHARED_CHANNEL_MAX_VALUE_BYTES: number;
|
|
16
|
-
export declare const SHARED_CHANNEL_SOFT_VALUE_WARN = 1024;
|
|
17
|
-
export declare const SHARED_CHANNEL_MAX_FILE_BYTES: number;
|
|
18
|
-
export declare const SHARED_CHANNEL_TTL_DAYS = 30;
|
|
19
|
-
export type WriteSharedEntryResult = {
|
|
20
|
-
readonly ok: true;
|
|
21
|
-
readonly entry: SharedChannelEntry;
|
|
22
|
-
readonly channelSize: number;
|
|
23
|
-
readonly lastWriteWins: boolean;
|
|
24
|
-
readonly softWarning: boolean;
|
|
25
|
-
} | {
|
|
26
|
-
readonly ok: false;
|
|
27
|
-
readonly code: 'VALUE_TOO_LARGE' | 'INVALID_BATCH_ID' | 'WRITE_ERROR';
|
|
28
|
-
readonly message: string;
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* Write a shared entry to the per-batch channel file. Atomic-write
|
|
32
|
-
* (tmp + rename). Returns the new entry + channel size + last-write-wins
|
|
33
|
-
* flag (true if the key already existed; the new value overwrites).
|
|
34
|
-
*
|
|
35
|
-
* RL-25 size limit: `value ≥ 64KB` is hard-rejected with `VALUE_TOO_LARGE`.
|
|
36
|
-
* `value > 1KB and < 64KB` is a soft warning (returned in the result).
|
|
37
|
-
*/
|
|
38
|
-
export declare function writeSharedEntry(opts: {
|
|
39
|
-
projectRoot: string;
|
|
40
|
-
sid: string;
|
|
41
|
-
rid: string;
|
|
42
|
-
batchId: string;
|
|
43
|
-
key: string;
|
|
44
|
-
from: string;
|
|
45
|
-
value: Record<string, unknown>;
|
|
46
|
-
}): WriteSharedEntryResult;
|
|
47
|
-
/**
|
|
48
|
-
* Read the shared channel for a batch. Returns the channel with all
|
|
49
|
-
* matching entries. Filters: `--since` (ISO8601) and `--key` (glob
|
|
50
|
-
* pattern; simple `*` wildcard, no regex).
|
|
51
|
-
*
|
|
52
|
-
* If the channel file does not exist, returns an empty channel
|
|
53
|
-
* (this is the dispatcher's "fresh batch" view).
|
|
54
|
-
*/
|
|
55
|
-
export declare function readSharedChannel(opts: {
|
|
56
|
-
projectRoot: string;
|
|
57
|
-
sid: string;
|
|
58
|
-
rid: string;
|
|
59
|
-
batchId: string;
|
|
60
|
-
since?: string;
|
|
61
|
-
keyPattern?: string;
|
|
62
|
-
}): SharedChannel;
|
|
63
|
-
/**
|
|
64
|
-
* Garbage-collect a single channel. Returns true if the file was
|
|
65
|
-
* deleted, false if it did not exist.
|
|
66
|
-
*/
|
|
67
|
-
export declare function gcChannel(opts: {
|
|
68
|
-
projectRoot: string;
|
|
69
|
-
sid: string;
|
|
70
|
-
rid: string;
|
|
71
|
-
batchId: string;
|
|
72
|
-
}): boolean;
|
|
73
|
-
/**
|
|
74
|
-
* Check if a channel file is older than `SHARED_CHANNEL_TTL_DAYS` days
|
|
75
|
-
* and should be GC'd as an orphan. Returns true if file is missing
|
|
76
|
-
* (already GC'd) or older than TTL.
|
|
77
|
-
*/
|
|
78
|
-
export declare function isOrphanChannel(opts: {
|
|
79
|
-
projectRoot: string;
|
|
80
|
-
sid: string;
|
|
81
|
-
rid: string;
|
|
82
|
-
batchId: string;
|
|
83
|
-
now?: Date;
|
|
84
|
-
}): boolean;
|
|
85
|
-
/**
|
|
86
|
-
* Compile a simple key pattern with `*` wildcards to a matcher. Only
|
|
87
|
-
* `*` is special; everything else is a literal. `*` matches zero or
|
|
88
|
-
* more characters. Examples:
|
|
89
|
-
* "rd.*" matches "rd.completed", "rd.found-blocker"
|
|
90
|
-
* "*.completed" matches "rd.completed", "qa.completed"
|
|
91
|
-
* "*" matches everything
|
|
92
|
-
*/
|
|
93
|
-
export declare function compileKeyPattern(pattern: string): (key: string) => boolean;
|