peaks-loop 4.0.47 → 4.0.48
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 +24 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/agents/karpathy-reviewer.md +11 -10
- package/dist/cli/cli-helpers.d.ts +34 -0
- package/dist/cli/cli-helpers.js +57 -0
- package/dist/cli/commands/code-job-shape-commands.js +8 -0
- package/dist/cli/commands/code-runtime-commands.js +48 -8
- package/dist/cli/commands/compact-command.js +112 -0
- package/dist/cli/commands/config-commands.js +15 -9
- package/dist/cli/commands/dashboard-long-run.js +6 -0
- package/dist/cli/commands/dispatch-commands.js +11 -1
- package/dist/cli/commands/doctor/invoke-from-code.js +6 -0
- package/dist/cli/commands/hooks-commands.js +4 -4
- package/dist/cli/commands/job-commands.js +8 -0
- package/dist/cli/commands/loop-eval-commands.js +15 -0
- package/dist/cli/commands/perf-audit-commands.js +2 -0
- package/dist/cli/commands/playwright-commands.js +12 -0
- package/dist/cli/commands/prd-commands.js +1 -1
- package/dist/cli/commands/qa-commands.js +22 -0
- package/dist/cli/commands/request-commands.js +8 -0
- package/dist/cli/commands/scan-commands.js +1 -1
- package/dist/cli/commands/security-audit-commands.js +2 -0
- package/dist/cli/commands/slice-integrate-commands.js +5 -0
- package/dist/cli/commands/statusline-commands.js +44 -4
- package/dist/cli/commands/sub-agent/detached.d.ts +14 -1
- package/dist/cli/commands/sub-agent/detached.js +47 -22
- package/dist/cli/commands/sub-agent-shutdown-commands.js +11 -0
- package/dist/cli/commands/verdict-aggregate-command.js +95 -13
- package/dist/cli/commands/workflow-commands.js +1 -1
- package/dist/cli/index.js +5 -45
- package/dist/services/artifacts/artifact-prerequisites.d.ts +38 -7
- package/dist/services/artifacts/artifact-prerequisites.js +130 -65
- package/dist/services/artifacts/request-artifact-service.d.ts +8 -0
- package/dist/services/artifacts/request-artifact-service.js +18 -8
- package/dist/services/artifacts/request-artifact-state-helpers.d.ts +57 -0
- package/dist/services/artifacts/request-artifact-state-helpers.js +91 -10
- package/dist/services/audit-independent/perf-audit-service.d.ts +9 -0
- package/dist/services/audit-independent/perf-audit-service.js +27 -5
- package/dist/services/audit-independent/security-audit-service.d.ts +12 -2
- package/dist/services/audit-independent/security-audit-service.js +28 -6
- package/dist/services/code/auto-compact-lifecycle.d.ts +119 -0
- package/dist/services/code/auto-compact-lifecycle.js +169 -0
- package/dist/services/code/auto-compact-orchestrator.js +13 -2
- package/dist/services/code/compact-event-settle.d.ts +122 -0
- package/dist/services/code/compact-event-settle.js +219 -0
- package/dist/services/compact-history/compact-history-service.d.ts +14 -0
- package/dist/services/config/config-restore.d.ts +12 -1
- package/dist/services/config/config-restore.js +35 -4
- package/dist/services/config/config-rollback.js +6 -1
- package/dist/services/context/harness-context-witness.d.ts +310 -0
- package/dist/services/context/harness-context-witness.js +606 -0
- package/dist/services/evidence/evidence-generator.js +86 -49
- package/dist/services/final-review/final-review-service.d.ts +9 -0
- package/dist/services/final-review/final-review-service.js +36 -12
- package/dist/services/ide/ide-registry.d.ts +19 -0
- package/dist/services/ide/ide-registry.js +21 -0
- package/dist/services/job/job-state-store.js +7 -0
- package/dist/services/polyrepo/polyrepo-dispatcher.js +11 -0
- package/dist/services/prd/handoff-auto-regen.js +31 -27
- package/dist/services/prd/handoff-frontmatter.d.ts +44 -0
- package/dist/services/prd/handoff-frontmatter.js +75 -0
- package/dist/services/prd/handoff-service.d.ts +41 -2
- package/dist/services/prd/handoff-service.js +81 -8
- package/dist/services/prd/handoff-types.d.ts +3 -2
- package/dist/services/prd/handoff-types.js +3 -2
- package/dist/services/qa/qa-business-review-state.js +9 -0
- package/dist/services/scan/karpathy-service.js +2 -2
- package/dist/services/session/session-checkpoint-service.js +8 -0
- package/dist/services/skill/resume-detector.js +29 -11
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +6 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +61 -2
- package/dist/services/skills/hooks-settings-service.js +14 -4
- package/dist/services/skills/session-start-hook-constants.d.ts +45 -0
- package/dist/services/skills/session-start-hook-constants.js +45 -0
- package/dist/services/skills/skill-statusline-service.d.ts +14 -0
- package/dist/services/slice/slice-check-service.js +29 -11
- package/dist/services/slice/slice-review-state.js +8 -0
- package/dist/services/workflow/pipeline-verify-gate-support.d.ts +47 -10
- package/dist/services/workflow/pipeline-verify-gate-support.js +212 -93
- package/dist/services/workflow/pipeline-verify-service.js +24 -23
- package/dist/services/workflow/pipeline-verify-types.d.ts +10 -3
- package/dist/services/workspace/claude-settings-template.d.ts +56 -8
- package/dist/services/workspace/claude-settings-template.js +98 -20
- package/dist/services/workspace/workspace-claude-settings-materializer.js +78 -7
- package/package.json +6 -6
- package/skills/bee/peaks-prd/SKILL.md +7 -5
- package/skills/bee/peaks-qa/SKILL.md +5 -5
- package/skills/bee/peaks-qa/references/qa-runbook.md +2 -2
- package/skills/bee/peaks-qa/references/qa-transition-gates.md +7 -7
- package/skills/bee/peaks-rd/SKILL.md +8 -6
- package/skills/bee/peaks-rd/references/artifact-per-request.md +2 -2
- package/skills/bee/peaks-rd/references/parallel-review-fanout.md +7 -5
- package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +13 -13
- package/skills/bee/peaks-rd/references/rd-runbook.md +9 -5
- package/skills/bee/peaks-rd/references/rd-transition-gates.md +9 -7
- package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +6 -6
- package/skills/peaks-code/SKILL.md +1 -1
- package/skills/peaks-code/references/a2a-artifact-mapping.md +3 -3
- package/skills/peaks-code/references/local-artifact-workspace.md +1 -1
- package/skills/peaks-code/references/resume-detection.md +13 -7
- package/skills/peaks-code/references/runbook.md +3 -2
- package/skills/peaks-code/references/session-overload-signal-index.md +2 -1
- package/skills/peaks-code/references/workflow-gates-and-types.md +8 -6
|
@@ -4,11 +4,51 @@ import { aggregateVerdict } from '../../services/verdict/verdict-aggregator.js';
|
|
|
4
4
|
import { parseKarpathyEnvelope, parseMutEnvelope, parseQaEnvelope, parseSecurityEnvelope, parsePerfEnvelope, envelopesToAggregatorInput } from '../../services/verdict/envelopes.js';
|
|
5
5
|
import { addJsonOption, printResult } from '../cli-helpers.js';
|
|
6
6
|
import { fail, ok } from 'peaks-loop-shared/result';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { isUnsafePathInput } from '../../shared/path-safety.js';
|
|
8
|
+
import { DEFAULT_REQUEST_TYPE } from '../../services/artifacts/artifact-prerequisites.js';
|
|
9
|
+
import { REQUEST_ID_PATTERN } from '../../services/artifacts/request-artifact-service.js';
|
|
10
|
+
import { contractEvidencePaths } from '../../services/workflow/pipeline-verify-gate-support.js';
|
|
11
|
+
// Slice `2026-09-14-audit-artifact-rid-scoping`: the audit/review evidence
|
|
12
|
+
// filenames carry the rid. The ridless names are the pre-rid locations and
|
|
13
|
+
// stay readable during the back-compat window. `mut/` is NOT rid-scoped —
|
|
14
|
+
// see `MUT_REPORT` in `artifact-prerequisites.ts`: no producer in the repo
|
|
15
|
+
// can write a rid-scoped mut report, so probing a templated name here would
|
|
16
|
+
// look for a file that no writer can create.
|
|
17
|
+
//
|
|
18
|
+
// The candidate lists are read FROM the contract — the same
|
|
19
|
+
// `contractEvidencePaths()` the verify pipeline resolves with — rather than
|
|
20
|
+
// hand-written here. Hand-written lists held two of the contract's THREE
|
|
21
|
+
// tiers, so a session whose security/perf evidence sits at the oldest tier
|
|
22
|
+
// (`rd/security-review.md`, `rd/perf-baseline.md` — the live shape of three
|
|
23
|
+
// sessions on disk) reported `block` while `peaks request transition`
|
|
24
|
+
// accepted the same tree, and the hint named a legacy location it had never
|
|
25
|
+
// probed. One table, one answer.
|
|
10
26
|
const MUT_REL = 'mut/mut-report.json';
|
|
11
27
|
const QA_REL = 'qa/test-reports';
|
|
28
|
+
/**
|
|
29
|
+
* The declared tiers for one artifact name, primary first. Probed by the name
|
|
30
|
+
* this command used before the rid-scoping (the table matches it as either
|
|
31
|
+
* the primary or a legacy tier), so the lookup survives the primary moving.
|
|
32
|
+
* `verdict aggregate` is request-type-agnostic and every fanout-trigger type
|
|
33
|
+
* declares these artifacts identically, so the default type's list is the
|
|
34
|
+
* full one; a name the table does not carry falls back to itself.
|
|
35
|
+
*/
|
|
36
|
+
function contractRels(probeName) {
|
|
37
|
+
return contractEvidencePaths('rd', 'qa-handoff', DEFAULT_REQUEST_TYPE, probeName) ?? [probeName];
|
|
38
|
+
}
|
|
39
|
+
const SECURITY_RELS = contractRels('audit/security.md');
|
|
40
|
+
const PERF_RELS = contractRels('audit/perf.md');
|
|
41
|
+
const KARPATHY_RELS = contractRels('rd/karpathy-review.md');
|
|
42
|
+
/**
|
|
43
|
+
* Sources whose absence must not read as `pass`. `mut` is excluded on
|
|
44
|
+
* purpose: `MUT_REPORT` carries `backCompat: true`, so the contract itself
|
|
45
|
+
* treats a missing mut report as a warning rather than a gate failure.
|
|
46
|
+
*/
|
|
47
|
+
const REQUIRED_SOURCES = [
|
|
48
|
+
{ key: 'security', source: 'security-audit', rels: SECURITY_RELS },
|
|
49
|
+
{ key: 'perf', source: 'perf-audit', rels: PERF_RELS },
|
|
50
|
+
{ key: 'karpathy', source: 'karpathy-reviewer', rels: KARPATHY_RELS }
|
|
51
|
+
];
|
|
12
52
|
export function registerVerdictAggregateCommands(program, io) {
|
|
13
53
|
const verdict = program.command('verdict').description('Aggregate the 5 envelope sources feeding peaks-code verdict logic');
|
|
14
54
|
addJsonOption(verdict
|
|
@@ -25,6 +65,27 @@ export function registerVerdictAggregateCommands(program, io) {
|
|
|
25
65
|
process.exitCode = 1;
|
|
26
66
|
return;
|
|
27
67
|
}
|
|
68
|
+
// The rid SELECTS THE FILE, so it has to be one path segment. This is the
|
|
69
|
+
// repo's own request-id guard — the one `request-artifact-service.ts`
|
|
70
|
+
// applies to every request artifact and the reason `peaks workflow
|
|
71
|
+
// verify-pipeline` refuses a hostile rid — and it was not applied to the
|
|
72
|
+
// joins below. Measured before it was: `--from-rid
|
|
73
|
+
// '../../../../../../ONLY-HERE'` read `<project>/ONLY-HERE.md`, a file
|
|
74
|
+
// outside `.peaks/`, as this slice's security AND perf evidence.
|
|
75
|
+
if (!REQUEST_ID_PATTERN.test(rid)) {
|
|
76
|
+
printResult(io, fail('verdict.aggregate', 'RID_INVALID', `Invalid request id: ${rid} (expected letters, digits, dots, underscores, or dashes)`, {}, ['Pass the rid of the slice whose evidence you want aggregated, e.g. 2026-09-14-some-slug']), options.json);
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// `--sid` names the session DIRECTORY and is the same axis. Reject
|
|
81
|
+
// anything that is not one safe path segment rather than pinning a
|
|
82
|
+
// format: the default here is the deliberately non-format value
|
|
83
|
+
// `default`.
|
|
84
|
+
if (isUnsafePathInput(sid)) {
|
|
85
|
+
printResult(io, fail('verdict.aggregate', 'SID_INVALID', `Invalid session id: ${sid} (must be a single path segment)`, {}, ['Pass the session id, e.g. 2026-09-14-session-abc123']), options.json);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
28
89
|
try {
|
|
29
90
|
const sources = {
|
|
30
91
|
// v2.13.3 AC-1: use the canonical markdown-aware parser from
|
|
@@ -34,9 +95,9 @@ export function registerVerdictAggregateCommands(program, io) {
|
|
|
34
95
|
// returned `verdict: warn, violations: []` — the aggregator
|
|
35
96
|
// then produced `reasons: []` and `verdict: 'pass'` even when
|
|
36
97
|
// the audit had flagged HIGH violations.
|
|
37
|
-
security: readAudit(projectRoot, sid,
|
|
38
|
-
perf: readAudit(projectRoot, sid,
|
|
39
|
-
karpathy: readAudit(projectRoot, sid,
|
|
98
|
+
security: readAudit(projectRoot, sid, rid, SECURITY_RELS, parseSecurityEnvelope),
|
|
99
|
+
perf: readAudit(projectRoot, sid, rid, PERF_RELS, parsePerfEnvelope),
|
|
100
|
+
karpathy: readAudit(projectRoot, sid, rid, KARPATHY_RELS, (m) => parseKarpathyEnvelope(m)),
|
|
40
101
|
mut: await readMut(projectRoot, sid),
|
|
41
102
|
qa: readQa(projectRoot, sid, rid)
|
|
42
103
|
};
|
|
@@ -55,7 +116,26 @@ export function registerVerdictAggregateCommands(program, io) {
|
|
|
55
116
|
mut: sources.mut !== null ? 'present' : 'missing',
|
|
56
117
|
qa: sources.qa !== null ? 'present' : 'missing'
|
|
57
118
|
};
|
|
58
|
-
|
|
119
|
+
// Fail closed. `aggregateVerdict()` starts its top-level verdict at
|
|
120
|
+
// `'pass'`, so a source that could not be read simply never enters the
|
|
121
|
+
// input and the run reports green. Before this slice the bare paths
|
|
122
|
+
// were the ones the writers wrote; after it the writers moved and this
|
|
123
|
+
// reader did not — so a missing security or perf audit became a `pass`
|
|
124
|
+
// for the loop evaluator (`services/loop/evaluator-dispatcher.ts`). A
|
|
125
|
+
// guard that cannot read its input must not report success.
|
|
126
|
+
const missingRequired = REQUIRED_SOURCES.filter((entry) => sources[entry.key] === null);
|
|
127
|
+
const missingReasons = missingRequired.map((entry) => ({
|
|
128
|
+
source: entry.source,
|
|
129
|
+
sources: [entry.source],
|
|
130
|
+
signal: 'block',
|
|
131
|
+
kind: 'missing-evidence',
|
|
132
|
+
hint: `${entry.rels.map((rel) => rel.replace('<rid>', rid)).join(' | ')} could not be read under .peaks/_runtime/${sid}/`
|
|
133
|
+
}));
|
|
134
|
+
printResult(io, ok('verdict.aggregate', {
|
|
135
|
+
verdict: missingReasons.length > 0 ? 'block' : result.verdict,
|
|
136
|
+
reasons: [...missingReasons, ...result.reasons],
|
|
137
|
+
sources: sourceFlags
|
|
138
|
+
}, [], []), options.json);
|
|
59
139
|
}
|
|
60
140
|
catch (error) {
|
|
61
141
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -64,12 +144,14 @@ export function registerVerdictAggregateCommands(program, io) {
|
|
|
64
144
|
}
|
|
65
145
|
});
|
|
66
146
|
}
|
|
67
|
-
function readAudit(projectRoot, sid,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
147
|
+
function readAudit(projectRoot, sid, rid, rels, parse) {
|
|
148
|
+
// Canonical rid-scoped location first, then the pre-rid locations.
|
|
149
|
+
for (const rel of rels) {
|
|
150
|
+
const path = join(projectRoot, '.peaks', '_runtime', sid, rel.replace('<rid>', rid));
|
|
151
|
+
if (existsSync(path))
|
|
152
|
+
return parse(readFileSync(path, 'utf8'));
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
73
155
|
}
|
|
74
156
|
async function readMut(projectRoot, sid) {
|
|
75
157
|
const path = join(projectRoot, '.peaks', '_runtime', sid, MUT_REL);
|
|
@@ -420,7 +420,7 @@ export function registerWorkflowCommands(program, io) {
|
|
|
420
420
|
// semantics, role-based auth).
|
|
421
421
|
addJsonOption(workflow
|
|
422
422
|
.command('skip')
|
|
423
|
-
.description('Skip specific gates for a request (RD/QA). Use --dry-run to preview without writing. Allowed gate names: QA / RD (phase shortcuts) or specific gate names (rd-request-exists,
|
|
423
|
+
.description('Skip specific gates for a request (RD/QA). Use --dry-run to preview without writing. Allowed gate names: QA / RD (phase shortcuts) or specific gate names (rd-request-exists, prd-handoff, bug-analysis, code-review, security-review, perf-baseline, qa-request-exists, test-cases, test-report). Three rules apply: (1) only docs/config/chore slices can skip; (2) skip is one-time per rid; (3) script callers must also pass --i-have-reviewed.')
|
|
424
424
|
.requiredOption('--rid <rid>', 'request identifier')
|
|
425
425
|
.requiredOption('--project <path>', 'project root path')
|
|
426
426
|
.requiredOption('--gates <list>', 'comma-separated gate names (e.g. "QA" or "QA,slice-check" or "code-review,security-review")')
|
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CommanderError } from 'commander';
|
|
2
2
|
import { createProgram } from './program.js';
|
|
3
3
|
import { getErrorMessage } from 'peaks-loop-shared/result';
|
|
4
|
-
import { printErrorEnvelope } from './cli-helpers.js';
|
|
4
|
+
import { printErrorEnvelope, printMissingRequiredOptionEnvelope, resolveInvokedCommandPath } from './cli-helpers.js';
|
|
5
5
|
const defaultIo = {
|
|
6
6
|
stdout: (text) => process.stdout.write(`${text}\n`),
|
|
7
7
|
stderr: (text) => process.stderr.write(`${text}\n`)
|
|
@@ -30,32 +30,6 @@ const argv = process.argv.slice(2);
|
|
|
30
30
|
const hasHelp = argv.some((arg) => arg === '--help' || arg === '-h');
|
|
31
31
|
const firstPositional = argv.find((arg) => !arg.startsWith('-'));
|
|
32
32
|
const program = createProgram();
|
|
33
|
-
/**
|
|
34
|
-
* The subcommand path the caller actually invoked, in the same tokens they
|
|
35
|
-
* typed (`release canary`). Derived from the registered command tree by
|
|
36
|
-
* consuming leading non-`-` argv tokens, so it never disagrees with what
|
|
37
|
-
* Commander dispatched on.
|
|
38
|
-
*
|
|
39
|
-
* Commander's `CommanderError` carries a code and a message but no command
|
|
40
|
-
* reference — `missingMandatoryOptionValue` is raised by the leaf command and
|
|
41
|
-
* throws out of `parseAsync` with nothing naming it. Without this walk the
|
|
42
|
-
* missing-option envelope could only say `command: "cli"`, which is the field
|
|
43
|
-
* the caller needs to act on.
|
|
44
|
-
*/
|
|
45
|
-
function resolveInvokedCommandPath() {
|
|
46
|
-
let cmd = program;
|
|
47
|
-
const parts = [];
|
|
48
|
-
for (const token of argv) {
|
|
49
|
-
if (token.startsWith('-'))
|
|
50
|
-
break;
|
|
51
|
-
const next = cmd.commands.find((c) => c.name() === token || c.aliases().includes(token));
|
|
52
|
-
if (next === undefined)
|
|
53
|
-
break;
|
|
54
|
-
parts.push(next.name());
|
|
55
|
-
cmd = next;
|
|
56
|
-
}
|
|
57
|
-
return parts.length > 0 ? parts.join(' ') : program.name();
|
|
58
|
-
}
|
|
59
33
|
if (hasHelp && firstPositional !== undefined) {
|
|
60
34
|
const registered = new Set(program.commands.map((c) => c.name()));
|
|
61
35
|
if (!registered.has(firstPositional)) {
|
|
@@ -86,24 +60,10 @@ program.parseAsync(process.argv).catch((error) => {
|
|
|
86
60
|
return;
|
|
87
61
|
}
|
|
88
62
|
if (error.code === 'commander.missingMandatoryOptionValue') {
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
// what values it takes. The flags string Commander formats carries both
|
|
94
|
-
// (`--percent <10|50>`), so it is worth extracting rather than
|
|
95
|
-
// paraphrasing: it is the option's own declaration.
|
|
96
|
-
const message = getErrorMessage(error);
|
|
97
|
-
const option = /required option '([^']+)'/.exec(message)?.[1];
|
|
98
|
-
const invoked = resolveInvokedCommandPath();
|
|
99
|
-
printErrorEnvelope(defaultIo, invoked, 'MISSING_REQUIRED_OPTION', option === undefined
|
|
100
|
-
? message
|
|
101
|
-
: `Missing required option '${option}' for \`peaks ${invoked}\`.`, { option: option ?? null }, [
|
|
102
|
-
option === undefined
|
|
103
|
-
? `Run \`peaks ${invoked} --help\` to see the options this command requires.`
|
|
104
|
-
: `Supply ${option} — it is required, so the command has no default for it.`,
|
|
105
|
-
`Run \`peaks ${invoked} --help\` for the option's accepted values and its siblings.`
|
|
106
|
-
]);
|
|
63
|
+
// See `printMissingRequiredOptionEnvelope` — the same builder backs the
|
|
64
|
+
// in-process test runner, so the envelope a test asserts is the envelope
|
|
65
|
+
// a user gets.
|
|
66
|
+
printMissingRequiredOptionEnvelope(defaultIo, resolveInvokedCommandPath(program, argv), getErrorMessage(error));
|
|
107
67
|
return;
|
|
108
68
|
}
|
|
109
69
|
if (error.code === 'commander.missingArgument' || error.code === 'commander.unknownCommand' || error.code === 'commander.unknownOption') {
|
|
@@ -29,18 +29,36 @@ export type ArtifactPrerequisite = {
|
|
|
29
29
|
*/
|
|
30
30
|
headingMustContain?: ReadonlyArray<string>;
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
32
|
+
* The immediately previous location of this artifact. When
|
|
33
|
+
* `relativePath` does not resolve on disk, the resolver tries this
|
|
34
|
+
* path before reporting the prereq as missing.
|
|
35
|
+
*
|
|
36
|
+
* This field is a published surface, not just a private hint:
|
|
37
|
+
* `verify-pipeline`'s `contractEvidencePaths()`
|
|
38
|
+
* (`src/services/workflow/pipeline-verify-gate-support.ts`) reads it
|
|
39
|
+
* to build its probe list, so it must keep meaning "the one path a
|
|
40
|
+
* previous release wrote". Do not repurpose it as a general list —
|
|
41
|
+
* add older tiers to `legacyRelativePaths` instead.
|
|
38
42
|
*
|
|
39
43
|
* Body checks (`mustContain` / `mustContainAny` /
|
|
40
44
|
* `headingMustContain`) apply to whichever path resolved — the
|
|
41
|
-
* gate does not distinguish which path served the file.
|
|
45
|
+
* gate does not distinguish which path served the file. That is
|
|
46
|
+
* deliberate: a legacy hit must keep the gate open, or existing
|
|
47
|
+
* sessions would fail the transition on upgrade.
|
|
42
48
|
*/
|
|
43
49
|
legacyRelativePath?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Further, older locations, tried **in declared order** after
|
|
52
|
+
* `legacyRelativePath`. Slice `2026-09-14-audit-artifact-rid-scoping`
|
|
53
|
+
* added this because the two audit artifacts have a two-release
|
|
54
|
+
* history behind their rid-scoped path: the pre-rid-scoping
|
|
55
|
+
* `audit/security.md` (v2.12.0) and, behind it, the v2.11.x
|
|
56
|
+
* `rd/security-review.md`. Three sessions on disk
|
|
57
|
+
* (`2026-09-06-session-a87ca4`, `2026-09-10-session-528a63`,
|
|
58
|
+
* `2026-09-12-session-e37ef0`) hold their security evidence only at
|
|
59
|
+
* the last of those, so the oldest tier still has to resolve.
|
|
60
|
+
*/
|
|
61
|
+
legacyRelativePaths?: ReadonlyArray<string>;
|
|
44
62
|
};
|
|
45
63
|
export type PrerequisiteCheckResult = {
|
|
46
64
|
ok: boolean;
|
|
@@ -75,4 +93,17 @@ export type CheckPrerequisitesOptions = {
|
|
|
75
93
|
requestType?: RequestType;
|
|
76
94
|
};
|
|
77
95
|
export declare function getPrerequisitesFor(role: RequestArtifactRole, newState: RequestArtifactState, requestType?: RequestType): ReadonlyArray<ArtifactPrerequisite>;
|
|
96
|
+
/**
|
|
97
|
+
* The contract's BODY checks for one prerequisite, applied to `body`. Returns
|
|
98
|
+
* one human-readable message per failed check, empty when the body satisfies
|
|
99
|
+
* the contract.
|
|
100
|
+
*
|
|
101
|
+
* Exported because this contract has a SECOND enforcer: `peaks workflow
|
|
102
|
+
* verify-pipeline` probes the same table for the paths it checks
|
|
103
|
+
* (`pipeline-verify-gate-support.ts`), and a resolver that probed only
|
|
104
|
+
* `existsSync` passed files this function rejects — a guard laxer than the
|
|
105
|
+
* contract it claims to read. Both callers now share this one implementation,
|
|
106
|
+
* so the checker cannot drift from the table again.
|
|
107
|
+
*/
|
|
108
|
+
export declare function prerequisiteBodyViolations(prerequisite: ArtifactPrerequisite, body: string): string[];
|
|
78
109
|
export declare function checkPrerequisites(options: CheckPrerequisitesOptions): Promise<PrerequisiteCheckResult>;
|
|
@@ -26,8 +26,17 @@ const BUG_ANALYSIS = {
|
|
|
26
26
|
description: 'Bug root-cause analysis (reproduction, affected paths, fix approach, regression test plan)',
|
|
27
27
|
mustContain: ['## Root cause', '## Fix approach']
|
|
28
28
|
};
|
|
29
|
+
// Slice `2026-09-14-audit-artifact-rid-scoping`: the rid is part of the
|
|
30
|
+
// filename (`code-review-<rid>.md`). A fixed per-session path cannot hold
|
|
31
|
+
// two slices' evidence at once — the second slice's write silently
|
|
32
|
+
// destroyed the first slice's on 2026-09-13, and the gate stayed green
|
|
33
|
+
// because it only checks that *some* file is there, not whose it is.
|
|
34
|
+
// The bare path stays accepted (see `legacyRelativePath`) so sessions
|
|
35
|
+
// written before this change — and any writer still emitting it — keep
|
|
36
|
+
// passing.
|
|
29
37
|
const CODE_REVIEW = {
|
|
30
|
-
relativePath: 'rd/code-review
|
|
38
|
+
relativePath: 'rd/code-review-<rid>.md',
|
|
39
|
+
legacyRelativePath: 'rd/code-review.md',
|
|
31
40
|
description: 'Code review evidence (CRITICAL/HIGH must be fixed before handoff)',
|
|
32
41
|
mustContain: ['## Findings', 'CRITICAL']
|
|
33
42
|
};
|
|
@@ -52,23 +61,31 @@ const PERF_BASELINE = {
|
|
|
52
61
|
// are dispatched as pre-RD audit runs that consume the public PRD
|
|
53
62
|
// handoff (`prd/handoff.md`) — see `AUDIT_REQUIRES_HANDOFF` below.
|
|
54
63
|
//
|
|
55
|
-
// Back-compat: the canonical location is `audit/security
|
|
56
|
-
// `audit/perf
|
|
57
|
-
// `
|
|
58
|
-
//
|
|
64
|
+
// Back-compat: the canonical location is `audit/security-<rid>.md`
|
|
65
|
+
// (and `audit/perf-<rid>.md`); the pre-rid-scoping `audit/security.md`
|
|
66
|
+
// (and `audit/perf.md`) plus the older `rd/security-review.md` (and
|
|
67
|
+
// `rd/perf-baseline.md`) are accepted via `legacyRelativePaths`, in
|
|
68
|
+
// that order, for the back-compat window.
|
|
59
69
|
const AUDIT_SECURITY = {
|
|
60
|
-
relativePath: 'audit/security
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
relativePath: 'audit/security-<rid>.md',
|
|
71
|
+
// Two legacy tiers behind the rid-scoped path: the pre-rid-scoping
|
|
72
|
+
// v2.12.0 location, then the v2.11.x one. Both are live on disk.
|
|
73
|
+
legacyRelativePath: 'audit/security.md',
|
|
74
|
+
legacyRelativePaths: ['rd/security-review.md'],
|
|
75
|
+
description: 'Independent security audit output (peaks-security-audit skill, v2.12.0+). Replaces the v2.11.x rd/security-review.md slot for fanout-trigger request types. The rid is part of the filename so two slices in one session do not collide; the bare and rd/-prefixed legacy locations are accepted during the back-compat window via legacyRelativePaths.',
|
|
63
76
|
// New canonical path writes a "## Verdict" header on the audit
|
|
64
77
|
// envelope. The legacy rd/security-review.md writes a "## Findings"
|
|
65
78
|
// header. Both pass the gate.
|
|
66
79
|
mustContainAny: ['## Verdict', '## Findings']
|
|
67
80
|
};
|
|
68
81
|
const AUDIT_PERF = {
|
|
69
|
-
relativePath: 'audit/perf
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
relativePath: 'audit/perf-<rid>.md',
|
|
83
|
+
// Same two-tier history as AUDIT_SECURITY. This is the pair that
|
|
84
|
+
// produced the 2026-09-13 evidence: `audit/perf.md` was overwritten by
|
|
85
|
+
// the second slice and the first slice's audit is unrecoverable.
|
|
86
|
+
legacyRelativePath: 'audit/perf.md',
|
|
87
|
+
legacyRelativePaths: ['rd/perf-baseline.md'],
|
|
88
|
+
description: 'Independent perf audit output (peaks-perf-audit skill, v2.12.0+). Replaces the v2.11.x rd/perf-baseline.md slot for fanout-trigger request types. The rid is part of the filename so two slices in one session do not collide; the bare and rd/-prefixed legacy locations are accepted during the back-compat window via legacyRelativePaths.',
|
|
72
89
|
// New schema writes "## Baseline" header; the legacy schema writes
|
|
73
90
|
// "## Results". Both pass the gate, as does the explicit
|
|
74
91
|
// no-perf-surface stub (slices whose surface is purely logic /
|
|
@@ -93,6 +110,16 @@ const AUDIT_PERF = {
|
|
|
93
110
|
// failure. The hard-fail behavior for `passed: false` is preserved
|
|
94
111
|
// (the body still has to carry `"passed": true` when present).
|
|
95
112
|
// v2.14.0 will remove `backCompat` and re-elevate missing → throw.
|
|
113
|
+
//
|
|
114
|
+
// Slice `2026-09-14-audit-artifact-rid-scoping` deliberately does NOT
|
|
115
|
+
// rid-scope this path, unlike the four audit/review artifacts above. The
|
|
116
|
+
// repo's own producer/reader constant is `mutReportPath()` in
|
|
117
|
+
// `packages/peaks-loop-mut/src/services/mut/report-loader.ts`, which names
|
|
118
|
+
// `mut/mut-report.json`, and `peaks mut run`'s `--out` is supplied by the
|
|
119
|
+
// caller. A `<rid>`-templated requirement here would be a name no producer
|
|
120
|
+
// in the repo can write — a gate that exists only in prose, sitting behind
|
|
121
|
+
// `backCompat: true` so it gates nothing at all. If mut ever gains a
|
|
122
|
+
// rid-scoped producer, the constant and this path move together.
|
|
96
123
|
const MUT_REPORT = {
|
|
97
124
|
relativePath: 'mut/mut-report.json',
|
|
98
125
|
description: 'peaks-mut mutation + weak-assertion report (Plan 2, v2.12.0+). v2.13.1: required at rd:qa-handoff for feature / bugfix / refactor slices. v2.13.2: back-compat window — missing file downgrades to a warning (1-minor-release); passed:false still throws. The body must carry `"passed": true` when present; failed runs are blocked at this gate. CONFIG / DOCS / CHORE slices retain the legacy "no acceptance surface" exemption.',
|
|
@@ -105,10 +132,11 @@ const MUT_REPORT = {
|
|
|
105
132
|
backCompat: true
|
|
106
133
|
};
|
|
107
134
|
// v2.12.0 Group B Tier 5 — gate that the peaks-prd handoff (the
|
|
108
|
-
// immutable handoff capsule at `prd/handoff
|
|
135
|
+
// immutable, per-slice handoff capsule at `prd/handoff-<rid>.md`
|
|
136
|
+
// since slice `2026-09-14-prd-capsule-rid-scoping`) exists before any
|
|
109
137
|
// audit skill is allowed to consume it. The peaks-security-audit
|
|
110
|
-
// and peaks-perf-audit CLI commands read frontmatter from
|
|
111
|
-
//
|
|
138
|
+
// and peaks-perf-audit CLI commands read frontmatter from the
|
|
139
|
+
// capsule (AC-2.4 / AC-3.4); if the handoff is missing the
|
|
112
140
|
// audit skill aborts — and so should the prereq gate when those
|
|
113
141
|
// audits are required at rd:qa-handoff.
|
|
114
142
|
//
|
|
@@ -117,8 +145,21 @@ const MUT_REPORT = {
|
|
|
117
145
|
// the old form (no PRD handoff chain — config slices may run before
|
|
118
146
|
// PRD handoff exists for small CONFIG-only commits).
|
|
119
147
|
const AUDIT_REQUIRES_HANDOFF = {
|
|
120
|
-
relativePath: 'prd/handoff
|
|
121
|
-
|
|
148
|
+
relativePath: 'prd/handoff-<rid>.md',
|
|
149
|
+
// Slice `2026-09-14-prd-capsule-rid-scoping`: the rid is part of the
|
|
150
|
+
// filename, like the four audit/review artifacts above. A single slot per
|
|
151
|
+
// SESSION cannot hold two slices' capsules — `peaks prd handoff init`
|
|
152
|
+
// overwrote it on every call, and this gate stayed green because it pinned
|
|
153
|
+
// only `schemaVersion: 2` + `sha256:`, never WHOSE rid the file named.
|
|
154
|
+
// Measured on `2026-09-13-session-21878f`: a four-slice job passed this
|
|
155
|
+
// prerequisite on a capsule written for a different line of work.
|
|
156
|
+
// The bare path stays accepted (see `legacyRelativePath`) so the three
|
|
157
|
+
// sessions on disk that hold only `prd/handoff.md` keep passing. That tier
|
|
158
|
+
// is rid-blind by design — the scoping binds for capsules written from now
|
|
159
|
+
// on, and every producer (`handoff-service.initHandoff`,
|
|
160
|
+
// `handoff-auto-regen`, `evidence generate`) writes the rid-scoped name.
|
|
161
|
+
legacyRelativePath: 'prd/handoff.md',
|
|
162
|
+
description: 'PRD handoff capsule (v2.12.0+) — peaks-security-audit / peaks-perf-audit both read frontmatter from this file. Must exist before audit prereqs are evaluated at rd:qa-handoff. The rid is part of the filename so two slices in one session do not collide; the bare pre-rid-scoping location is accepted as the legacy tier.',
|
|
122
163
|
// Empty `mustContain` is fine — file existence is the contract.
|
|
123
164
|
// The peaks-prd handoff service writes frontmatter with
|
|
124
165
|
// `schemaVersion: 2` and a sha256 fingerprint; we pin a
|
|
@@ -138,7 +179,8 @@ const AUDIT_REQUIRES_HANDOFF = {
|
|
|
138
179
|
// header remains a substring match (it is the file's own gate header, not
|
|
139
180
|
// a structural section anchor).
|
|
140
181
|
const KARPATHY_REVIEW = {
|
|
141
|
-
relativePath: 'rd/karpathy-review
|
|
182
|
+
relativePath: 'rd/karpathy-review-<rid>.md',
|
|
183
|
+
legacyRelativePath: 'rd/karpathy-review.md',
|
|
142
184
|
description: 'RD-side karpathy review (peaks-rd 5-way fanout) — must contain a "## Karpathy-Gate" header AND the 4 guideline section markers (Think Before Coding / Simplicity First / Surgical Changes / Goal-Driven Execution) as actual markdown headings. Per karpathy §1 / §3.',
|
|
143
185
|
mustContain: ['## Karpathy-Gate'],
|
|
144
186
|
headingMustContain: [
|
|
@@ -328,6 +370,57 @@ async function resolvePrerequisiteAbsolutePath(sessionRoot, prerequisite, reques
|
|
|
328
370
|
const match = entries.find((name) => /^\d+-/.test(name) && name.endsWith(targetSuffix));
|
|
329
371
|
return match ? join(dir, match) : null;
|
|
330
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* The contract's BODY checks for one prerequisite, applied to `body`. Returns
|
|
375
|
+
* one human-readable message per failed check, empty when the body satisfies
|
|
376
|
+
* the contract.
|
|
377
|
+
*
|
|
378
|
+
* Exported because this contract has a SECOND enforcer: `peaks workflow
|
|
379
|
+
* verify-pipeline` probes the same table for the paths it checks
|
|
380
|
+
* (`pipeline-verify-gate-support.ts`), and a resolver that probed only
|
|
381
|
+
* `existsSync` passed files this function rejects — a guard laxer than the
|
|
382
|
+
* contract it claims to read. Both callers now share this one implementation,
|
|
383
|
+
* so the checker cannot drift from the table again.
|
|
384
|
+
*/
|
|
385
|
+
export function prerequisiteBodyViolations(prerequisite, body) {
|
|
386
|
+
const violations = [];
|
|
387
|
+
const lowered = body.toLowerCase();
|
|
388
|
+
if (prerequisite.mustContain && prerequisite.mustContain.length > 0) {
|
|
389
|
+
const missingMarkers = prerequisite.mustContain.filter((marker) => !lowered.includes(marker.toLowerCase()));
|
|
390
|
+
if (missingMarkers.length > 0) {
|
|
391
|
+
violations.push(`${prerequisite.description} — missing section(s): ${missingMarkers.join(', ')}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (prerequisite.headingMustContain && prerequisite.headingMustContain.length > 0) {
|
|
395
|
+
// Slice 2.6.1.F: a line beginning with `#`, `##`, or `###` followed by
|
|
396
|
+
// the marker (case-insensitive). Fenced code blocks are NOT excluded
|
|
397
|
+
// here — a "heading" inside a code fence is rare and, when present,
|
|
398
|
+
// should still be reported as missing to keep the contract strict.
|
|
399
|
+
const headingLines = body
|
|
400
|
+
.split('\n')
|
|
401
|
+
.map((line) => line.trim())
|
|
402
|
+
.filter((line) => /^#{1,3}\s+/.test(line));
|
|
403
|
+
const loweredHeadings = headingLines.map((h) => h.toLowerCase());
|
|
404
|
+
const missingHeadings = prerequisite.headingMustContain.filter((marker) => !loweredHeadings.some((h) => h.includes(marker.toLowerCase())));
|
|
405
|
+
if (missingHeadings.length > 0) {
|
|
406
|
+
violations.push(`${prerequisite.description} — missing heading(s): ${missingHeadings.join(', ')}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (prerequisite.mustContainAny && prerequisite.mustContainAny.length > 0) {
|
|
410
|
+
const hitAny = prerequisite.mustContainAny.some((marker) => lowered.includes(marker.toLowerCase()));
|
|
411
|
+
if (!hitAny) {
|
|
412
|
+
violations.push(`${prerequisite.description} — none of the escape-hatch markers present: ${prerequisite.mustContainAny.join(', ')}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return violations;
|
|
416
|
+
}
|
|
417
|
+
/** True when `prerequisite` declares any body check at all. Guards the read so
|
|
418
|
+
* a prereq with no body contract is never opened (unchanged behaviour). */
|
|
419
|
+
function hasBodyChecks(prerequisite) {
|
|
420
|
+
return (prerequisite.mustContain?.length ?? 0) > 0
|
|
421
|
+
|| (prerequisite.headingMustContain?.length ?? 0) > 0
|
|
422
|
+
|| (prerequisite.mustContainAny?.length ?? 0) > 0;
|
|
423
|
+
}
|
|
331
424
|
export async function checkPrerequisites(options) {
|
|
332
425
|
const requirements = getPrerequisitesFor(options.role, options.newState, options.requestType);
|
|
333
426
|
if (requirements.length === 0) {
|
|
@@ -369,45 +462,10 @@ export async function checkPrerequisites(options) {
|
|
|
369
462
|
missing.push({ path: relative, description: prerequisite.description });
|
|
370
463
|
continue;
|
|
371
464
|
}
|
|
372
|
-
if (prerequisite
|
|
465
|
+
if (hasBodyChecks(prerequisite)) {
|
|
373
466
|
const body = await readFile(absolute, 'utf8');
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
if (missingMarkers.length > 0) {
|
|
377
|
-
missing.push({
|
|
378
|
-
path: relative,
|
|
379
|
-
description: `${prerequisite.description} — missing section(s): ${missingMarkers.join(', ')}`
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
if (prerequisite.headingMustContain && prerequisite.headingMustContain.length > 0) {
|
|
384
|
-
const body = await readFile(absolute, 'utf8');
|
|
385
|
-
// Slice 2.6.1.F: a line beginning with `#`, `##`, or `###` followed by
|
|
386
|
-
// the marker (case-insensitive). Fenced code blocks are NOT excluded
|
|
387
|
-
// here — a "heading" inside a code fence is rare and, when present,
|
|
388
|
-
// should still be reported as missing to keep the contract strict.
|
|
389
|
-
const headingLines = body
|
|
390
|
-
.split('\n')
|
|
391
|
-
.map((line) => line.trim())
|
|
392
|
-
.filter((line) => /^#{1,3}\s+/.test(line));
|
|
393
|
-
const loweredHeadings = headingLines.map((h) => h.toLowerCase());
|
|
394
|
-
const missingHeadings = prerequisite.headingMustContain.filter((marker) => !loweredHeadings.some((h) => h.includes(marker.toLowerCase())));
|
|
395
|
-
if (missingHeadings.length > 0) {
|
|
396
|
-
missing.push({
|
|
397
|
-
path: relative,
|
|
398
|
-
description: `${prerequisite.description} — missing heading(s): ${missingHeadings.join(', ')}`
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
if (prerequisite.mustContainAny && prerequisite.mustContainAny.length > 0) {
|
|
403
|
-
const body = await readFile(absolute, 'utf8');
|
|
404
|
-
const lowered = body.toLowerCase();
|
|
405
|
-
const hitAny = prerequisite.mustContainAny.some((marker) => lowered.includes(marker.toLowerCase()));
|
|
406
|
-
if (!hitAny) {
|
|
407
|
-
missing.push({
|
|
408
|
-
path: relative,
|
|
409
|
-
description: `${prerequisite.description} — none of the escape-hatch markers present: ${prerequisite.mustContainAny.join(', ')}`
|
|
410
|
-
});
|
|
467
|
+
for (const description of prerequisiteBodyViolations(prerequisite, body)) {
|
|
468
|
+
missing.push({ path: relative, description });
|
|
411
469
|
}
|
|
412
470
|
}
|
|
413
471
|
}
|
|
@@ -454,12 +512,13 @@ function emitPrereqTransitionEvent(opts) {
|
|
|
454
512
|
* (e.g. `001-<rid>.md`) at every tier. Returns the matched absolute
|
|
455
513
|
* path, or null when nothing matches.
|
|
456
514
|
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
* `audit/security.md`
|
|
515
|
+
* Back-compat: once neither primary session-root tier resolved, the
|
|
516
|
+
* resolver walks `prerequisite.legacyRelativePaths` in declared order
|
|
517
|
+
* and tries each at BOTH session roots before declaring the prereq
|
|
518
|
+
* missing. This covers both artifacts that moved location
|
|
519
|
+
* (`rd/security-review.md` → `audit/security.md`) and ones that later
|
|
520
|
+
* gained a rid in the filename (`audit/security.md` →
|
|
521
|
+
* `audit/security-<rid>.md`, slice `2026-09-14-audit-artifact-rid-scoping`).
|
|
463
522
|
*/
|
|
464
523
|
async function resolvePrerequisiteAbsolutePathWithFallback(canonicalSessionRoot, legacySessionRoot, prerequisite, requestId) {
|
|
465
524
|
const roots = [canonicalSessionRoot, legacySessionRoot];
|
|
@@ -471,13 +530,19 @@ async function resolvePrerequisiteAbsolutePathWithFallback(canonicalSessionRoot,
|
|
|
471
530
|
if (found !== null)
|
|
472
531
|
return found;
|
|
473
532
|
}
|
|
474
|
-
// Second pass:
|
|
475
|
-
// root
|
|
533
|
+
// Second pass: the single previous location, then any further older
|
|
534
|
+
// ones, at every root — newest first, so a session that somehow holds
|
|
535
|
+
// both a pre-rid and a v2.11.x file resolves to the newer shape.
|
|
536
|
+
// Only the relativePath is swapped — the same numbered-prefix
|
|
476
537
|
// tolerance applies via the shared resolver.
|
|
477
|
-
|
|
538
|
+
const legacyTiers = [
|
|
539
|
+
...(prerequisite.legacyRelativePath !== undefined ? [prerequisite.legacyRelativePath] : []),
|
|
540
|
+
...(prerequisite.legacyRelativePaths ?? [])
|
|
541
|
+
];
|
|
542
|
+
for (const legacy of legacyTiers) {
|
|
478
543
|
const legacyPrereq = {
|
|
479
544
|
...prerequisite,
|
|
480
|
-
relativePath:
|
|
545
|
+
relativePath: legacy
|
|
481
546
|
};
|
|
482
547
|
for (const root of roots) {
|
|
483
548
|
if (root === null)
|
|
@@ -50,6 +50,14 @@ export type CreateRequestArtifactResult = {
|
|
|
50
50
|
*/
|
|
51
51
|
scopeDir: string;
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* F-1 (slice 025 security): reject rids that contain path separators, null
|
|
55
|
+
* bytes, or traversal sequences. A request id is a single path segment, so
|
|
56
|
+
* anything that builds a filename from one must test it against this first —
|
|
57
|
+
* it is exported so those call sites reuse it instead of re-declaring a copy
|
|
58
|
+
* that can drift.
|
|
59
|
+
*/
|
|
60
|
+
export declare const REQUEST_ID_PATTERN: RegExp;
|
|
53
61
|
export declare function createRequestArtifact(options: CreateRequestArtifactOptions): Promise<CreateRequestArtifactResult>;
|
|
54
62
|
export type RequestArtifactSummary = {
|
|
55
63
|
role: RequestArtifactRole;
|