@haiyangbg/buildbeat 3.3.1 → 4.0.0
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 +19 -0
- package/README.en.md +20 -124
- package/README.md +20 -124
- package/SKILL.md +73 -84
- package/docs/CAPABILITY-MATRIX.md +30 -39
- package/docs/MIGRATION.md +93 -0
- package/docs/README.md +8 -37
- package/docs/RELEASING.md +4 -3
- package/docs/v2/RFC-0001-product-definition.md +3 -0
- package/docs/v2/RFC-0002-domain-model.md +3 -0
- package/docs/v2/RFC-0003-workflow-policy.md +3 -0
- package/docs/v2/SPEC-0001-events-v1.md +3 -0
- package/docs/v2/guide/00-how-to-talk.en.md +12 -58
- package/docs/v2/guide/00-how-to-talk.md +12 -58
- package/docs/v2/guide/01-quickstart.en.md +41 -112
- package/docs/v2/guide/01-quickstart.md +41 -112
- package/docs/v2/guide/02-workflow-guide.en.md +13 -123
- package/docs/v2/guide/02-workflow-guide.md +14 -122
- package/docs/v2/guide/03-policy-guide.en.md +6 -50
- package/docs/v2/guide/03-policy-guide.md +6 -50
- package/docs/v2/guide/04-adapter-guide.en.md +7 -60
- package/docs/v2/guide/04-adapter-guide.md +7 -60
- package/docs/v2/guide/05-worker-contract.en.md +6 -53
- package/docs/v2/guide/05-worker-contract.md +7 -52
- package/docs/v2/guide/06-evidence-guide.en.md +8 -44
- package/docs/v2/guide/06-evidence-guide.md +9 -43
- package/docs/v2/guide/07-approval-guide.en.md +8 -110
- package/docs/v2/guide/07-approval-guide.md +8 -121
- package/docs/v2/guide/09-security-boundaries.en.md +13 -37
- package/docs/v2/guide/09-security-boundaries.md +13 -37
- package/docs/v2/guide/10-recovery.en.md +12 -96
- package/docs/v2/guide/10-recovery.md +12 -96
- package/docs/v2/guide/11-session-handoff.en.md +7 -78
- package/docs/v2/guide/11-session-handoff.md +7 -78
- package/docs/v2/guide/12-without-runtime.en.md +46 -0
- package/docs/v2/guide/12-without-runtime.md +46 -0
- package/docs/v2/guide/README.en.md +16 -36
- package/docs/v2/guide/README.md +16 -36
- package/example/AGENTS.md +15 -69
- package/example/README.md +8 -23
- package/example/delivery/envelope/prompts/builder.md +3 -1
- package/example/delivery/envelope/prompts/fixer.md +3 -1
- package/example/delivery/envelope/prompts/reviewer.md +3 -1
- package/example//346/214/207/346/214/245/345/217/260.md +8 -40
- package/package.json +2 -1
- package/src/v2/adapters/notification-payload.js +40 -0
- package/src/v2/adapters/shell.js +3 -1
- package/src/v2/cli/run-config-check.js +129 -45
- package/src/v2/cli/run.js +821 -252
- package/src/v2/engine/reducer.js +2 -0
- package/src/v2/engine/risk-preset.js +14 -47
- package/src/v2/engine/workflow.js +177 -185
- package/src/v2/evidence/collector.js +12 -1
- package/src/v2/evidence/image.js +346 -0
- package/src/v2/policy/policy.js +219 -286
- package/src/v2/presets/software-delivery.yaml +1 -16
- package/src/v2/runtime/cache.js +22 -1
- package/src/v2/runtime/decisions.js +185 -31
- package/src/v2/runtime/findings.js +4 -1
- package/src/v2/runtime/notify.js +42 -36
- package/src/v2/runtime/orchestrator.js +811 -297
- package/src/v2/runtime/overview.js +163 -34
- package/src/v2/runtime/release.js +233 -0
- package/src/v2/runtime/run-record.js +1 -0
- package/src/v2/workspace/workspace-manager.js +8 -0
- package/templates/v2/AGENTS.md +15 -69
- package/templates/v2/BUILDBEAT.md +2 -13
- package/templates/v2/envelope/prompts/builder.md +3 -1
- package/templates/v2/envelope/prompts/fixer.md +3 -1
- package/templates/v2/envelope/prompts/reviewer.md +3 -1
- package/templates/v2/run-config.example.yaml +7 -5
- package/templates/v2/work.example.md +16 -0
- package/templates/v2//346/214/207/346/214/245/345/217/260.md +8 -40
- package/docs/v2/skill/01-principles.md +0 -26
- package/docs/v2/skill/02-project-layout.md +0 -31
- package/docs/v2/skill/03-collaboration-rules.md +0 -45
- package/docs/v2/skill/04-rhythm-and-rituals.md +0 -104
- package/docs/v2/skill/05-red-lines.md +0 -13
- package/docs/v2/skill/06-bootstrap-and-takeover.md +0 -84
- package/docs/v2/skill/07-templates-and-lessons.md +0 -24
- package/example/.buildbeat/observe.yaml +0 -31
- package/src/v2/observe/observe-config.js +0 -194
- package/src/v2/observe/observe-reducer.js +0 -117
- package/src/v2/observe/observe.js +0 -427
- package/src/v2/presets/observe.yaml +0 -45
- package/src/v2/presets/policies/ui-render-gate.yaml +0 -13
- package/src/v2/presets/release-readback.yaml +0 -36
- package/src/v2/presets/risk/controlled.yaml +0 -39
- package/src/v2/presets/risk/fast.yaml +0 -19
- package/src/v2/presets/risk/release.yaml +0 -21
- package/src/v2/presets/risk/standard.yaml +0 -28
- package/templates/ARCHITECTURE.md +0 -50
- package/templates/contracts/PROTOCOL.md +0 -28
- package/templates/pm/adr/ADR-0000-template.md +0 -25
- package/templates/pm/adr/README.md +0 -15
- package/templates/pm/decisions.md +0 -11
- package/templates/standards/CODE.md +0 -23
- package/templates/standards/DESIGN.md +0 -36
- package/templates/standards/REVIEW.md +0 -20
- package/templates/standards/STACK.md +0 -31
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
// observe v0 runner per docs/v2/RFC-0003-workflow-policy.md §8 (frozen
|
|
2
|
-
// semantics): providers produce contract-shaped Evidence (unreachable data is
|
|
3
|
-
// unverified, never silence); bands route log → readonly diagnose → intent
|
|
4
|
-
// DRAFT enqueue (never auto-executed); dismiss feeds back into drafting.
|
|
5
|
-
// Intent drafts and triage verdicts live in the Git plane so deleting
|
|
6
|
-
// .buildbeat/runtime/ loses no decision (invariant 23). One invocation runs
|
|
7
|
-
// one cycle; periodic operation is host-level (cron/CI) in v0.
|
|
8
|
-
|
|
9
|
-
import { createHash } from "node:crypto";
|
|
10
|
-
import {
|
|
11
|
-
existsSync,
|
|
12
|
-
mkdirSync,
|
|
13
|
-
readFileSync,
|
|
14
|
-
readdirSync,
|
|
15
|
-
writeFileSync,
|
|
16
|
-
} from "node:fs";
|
|
17
|
-
import { join, resolve } from "node:path";
|
|
18
|
-
|
|
19
|
-
import { createShellAdapter } from "../adapters/shell.js";
|
|
20
|
-
import { INTENT_DRAFT_STATES, TRIAGE_ACTIONS } from "../domain/model.js";
|
|
21
|
-
import { collectCommandEvidence } from "../evidence/collector.js";
|
|
22
|
-
import { EventLedger } from "../storage/event-ledger.js";
|
|
23
|
-
import { loadObserveConfig, severityRank } from "./observe-config.js";
|
|
24
|
-
import { OBSERVE_REDUCER } from "./observe-reducer.js";
|
|
25
|
-
import { toRepoRef } from "../runtime/repo-ref.js";
|
|
26
|
-
|
|
27
|
-
const OBSERVE_IDS = { run: "OBSERVE", work: "OBSERVE" };
|
|
28
|
-
const KERNEL_ACTOR = { kind: "kernel", id: "observe" };
|
|
29
|
-
const HEADER_PREFIX = "<!-- buildbeat-intent ";
|
|
30
|
-
const HEADER_SUFFIX = " -->";
|
|
31
|
-
|
|
32
|
-
export class ObserveError extends Error {
|
|
33
|
-
constructor(message) {
|
|
34
|
-
super(message);
|
|
35
|
-
this.name = "ObserveError";
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function observeLedgerPath(repoRoot) {
|
|
40
|
-
return join(repoRoot, ".buildbeat", "runtime", "observe", "events.jsonl");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function openObserveLedger(repoRoot) {
|
|
44
|
-
return EventLedger.open(observeLedgerPath(repoRoot), OBSERVE_REDUCER);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function intentsDir(repoRoot) {
|
|
48
|
-
return join(repoRoot, "delivery", "observe", "intents");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function parseIntentHeader(filePath, text) {
|
|
52
|
-
const firstLine = text.split("\n", 1)[0] ?? "";
|
|
53
|
-
if (!firstLine.startsWith(HEADER_PREFIX) || !firstLine.endsWith(HEADER_SUFFIX)) {
|
|
54
|
-
throw new ObserveError(
|
|
55
|
-
`intent draft ${filePath} has no buildbeat-intent header line; fix or remove the file`,
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
const body = firstLine.slice(HEADER_PREFIX.length, -HEADER_SUFFIX.length);
|
|
59
|
-
let header;
|
|
60
|
-
try {
|
|
61
|
-
header = JSON.parse(body);
|
|
62
|
-
} catch {
|
|
63
|
-
throw new ObserveError(`intent draft ${filePath} header is not valid JSON`);
|
|
64
|
-
}
|
|
65
|
-
if (!INTENT_DRAFT_STATES.includes(header.status)) {
|
|
66
|
-
throw new ObserveError(
|
|
67
|
-
`intent draft ${filePath} has unknown status: ${header.status}`,
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
return header;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function readIntentDrafts(repoRoot) {
|
|
74
|
-
const dir = intentsDir(repoRoot);
|
|
75
|
-
if (!existsSync(dir)) {
|
|
76
|
-
return [];
|
|
77
|
-
}
|
|
78
|
-
const drafts = [];
|
|
79
|
-
for (const name of readdirSync(dir).sort()) {
|
|
80
|
-
if (!name.endsWith(".md")) {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
const filePath = join(dir, name);
|
|
84
|
-
const text = readFileSync(filePath, "utf8");
|
|
85
|
-
drafts.push({ file: name, path: filePath, header: parseIntentHeader(filePath, text), text });
|
|
86
|
-
}
|
|
87
|
-
return drafts;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function renderHeader(header) {
|
|
91
|
-
return `${HEADER_PREFIX}${JSON.stringify(header)}${HEADER_SUFFIX}`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function writeDraft(filePath, header, body) {
|
|
95
|
-
writeFileSync(filePath, `${renderHeader(header)}\n${body}`, "utf8");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function rewriteHeader(draft, header, appendLine) {
|
|
99
|
-
const lines = draft.text.split("\n");
|
|
100
|
-
lines[0] = renderHeader(header);
|
|
101
|
-
let text = lines.join("\n");
|
|
102
|
-
if (appendLine) {
|
|
103
|
-
text = `${text.replace(/\n+$/, "\n")}${appendLine}\n`;
|
|
104
|
-
}
|
|
105
|
-
writeFileSync(draft.path, text, "utf8");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function fingerprintFor(provider, status) {
|
|
109
|
-
const key = [provider.id, provider.evidence.kind, provider.evidence.subject, status].join("|");
|
|
110
|
-
return createHash("sha256").update(key, "utf8").digest("hex").slice(0, 16);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function severityFor(provider, status) {
|
|
114
|
-
if (status === "passed") {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
return status === "failed" ? provider.severity.failed : provider.severity.unverified;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function execCommand({ name, command, args, timeoutMs, repoRoot, input }) {
|
|
121
|
-
const adapter = createShellAdapter({ name, command, args, timeoutMs });
|
|
122
|
-
return adapter.execute({
|
|
123
|
-
step: name,
|
|
124
|
-
worker: name,
|
|
125
|
-
workspacePath: repoRoot,
|
|
126
|
-
input,
|
|
127
|
-
timeoutMs,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function recordEvidence({ ledger, config, cycle, provider, execResult, kind, step, grade = "L2" }) {
|
|
132
|
-
const record = collectCommandEvidence({
|
|
133
|
-
runtimeDir: join(config.repoRoot, ".buildbeat", "runtime", "observe"),
|
|
134
|
-
runId: `cycle-${String(cycle).padStart(4, "0")}`,
|
|
135
|
-
step,
|
|
136
|
-
attempt: 1,
|
|
137
|
-
execResult,
|
|
138
|
-
subject: provider.evidence.subject,
|
|
139
|
-
kind,
|
|
140
|
-
grade,
|
|
141
|
-
});
|
|
142
|
-
ledger.append({
|
|
143
|
-
type: "EVIDENCE_RECORDED",
|
|
144
|
-
actor: { kind: "provider", id: provider.id },
|
|
145
|
-
data: {
|
|
146
|
-
evidenceRef: toRepoRef(config.repoRoot, record.location),
|
|
147
|
-
kind: record.kind,
|
|
148
|
-
subject: record.subject,
|
|
149
|
-
digest: record.digest,
|
|
150
|
-
status: record.status,
|
|
151
|
-
grade: record.grade,
|
|
152
|
-
provider: provider.id,
|
|
153
|
-
cycle,
|
|
154
|
-
},
|
|
155
|
-
...OBSERVE_IDS,
|
|
156
|
-
});
|
|
157
|
-
return record;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function draftBody({ provider, severity, record, fingerprint }) {
|
|
161
|
-
return `# Intent 草稿:${provider.id} 在 ${provider.evidence.subject} 上发现 ${provider.evidence.kind} 异常
|
|
162
|
-
|
|
163
|
-
- 来源 provider:\`${provider.id}\`(severity ${severity})
|
|
164
|
-
- 主题:\`${provider.evidence.subject}\`
|
|
165
|
-
- 证据:\`${record.digest}\`(${record.status};日志见 runtime observe 面,runtime 可删,digest 永续)
|
|
166
|
-
- 指纹:\`${fingerprint}\`
|
|
167
|
-
|
|
168
|
-
## 下一步(人分诊后才会发生任何执行)
|
|
169
|
-
|
|
170
|
-
- \`fix_now\`:接受后由人工带入 software-delivery Run(本草稿绝不自动执行);
|
|
171
|
-
- \`schedule\`:排期处理;
|
|
172
|
-
- \`dismiss\`:驳回——同指纹在严重度升级前不再重复入队。
|
|
173
|
-
|
|
174
|
-
分诊命令:
|
|
175
|
-
|
|
176
|
-
\`\`\`
|
|
177
|
-
node src/v2/cli/run.js observe triage --repo <repo> --intent <本文件相对路径> --action fix_now|schedule|dismiss --by <name>
|
|
178
|
-
\`\`\`
|
|
179
|
-
`;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function handleIntentBand({ ledger, config, cycle, provider, severity, record, fingerprint, now }) {
|
|
183
|
-
const dir = intentsDir(config.repoRoot);
|
|
184
|
-
const drafts = readIntentDrafts(config.repoRoot);
|
|
185
|
-
const existing = drafts.find((draft) => draft.header.fingerprint === fingerprint);
|
|
186
|
-
if (existing) {
|
|
187
|
-
const header = existing.header;
|
|
188
|
-
if (header.status === "dismissed") {
|
|
189
|
-
if (severityRank(severity) <= severityRank(header.dismissedSeverity ?? header.severity)) {
|
|
190
|
-
return { outcome: "suppressed", intentRef: relativeIntentRef(config.repoRoot, existing.path) };
|
|
191
|
-
}
|
|
192
|
-
const reopened = {
|
|
193
|
-
...header,
|
|
194
|
-
status: "draft",
|
|
195
|
-
severity,
|
|
196
|
-
evidenceDigest: record.digest,
|
|
197
|
-
updated: now,
|
|
198
|
-
};
|
|
199
|
-
delete reopened.dismissedSeverity;
|
|
200
|
-
delete reopened.triage;
|
|
201
|
-
rewriteHeader(
|
|
202
|
-
existing,
|
|
203
|
-
reopened,
|
|
204
|
-
`> ${now}:严重度升级为 ${severity}(此前以 ${header.dismissedSeverity ?? header.severity} 被 dismiss),草稿重新开启。`,
|
|
205
|
-
);
|
|
206
|
-
const intentRef = relativeIntentRef(config.repoRoot, existing.path);
|
|
207
|
-
ledger.append({
|
|
208
|
-
type: "INTENT_DRAFTED",
|
|
209
|
-
actor: KERNEL_ACTOR,
|
|
210
|
-
data: { intentRef, provider: provider.id, severity, fingerprint },
|
|
211
|
-
...OBSERVE_IDS,
|
|
212
|
-
});
|
|
213
|
-
return { outcome: "reopened", intentRef };
|
|
214
|
-
}
|
|
215
|
-
if (severityRank(severity) > severityRank(header.severity)) {
|
|
216
|
-
rewriteHeader(existing, { ...header, severity, evidenceDigest: record.digest, updated: now });
|
|
217
|
-
}
|
|
218
|
-
return { outcome: "already-queued", intentRef: relativeIntentRef(config.repoRoot, existing.path) };
|
|
219
|
-
}
|
|
220
|
-
mkdirSync(dir, { recursive: true });
|
|
221
|
-
const fileName = `INTENT-${fingerprint}.md`;
|
|
222
|
-
const filePath = join(dir, fileName);
|
|
223
|
-
const header = {
|
|
224
|
-
id: fingerprint,
|
|
225
|
-
fingerprint,
|
|
226
|
-
provider: provider.id,
|
|
227
|
-
kind: provider.evidence.kind,
|
|
228
|
-
subject: provider.evidence.subject,
|
|
229
|
-
severity,
|
|
230
|
-
status: "draft",
|
|
231
|
-
evidenceDigest: record.digest,
|
|
232
|
-
created: now,
|
|
233
|
-
updated: now,
|
|
234
|
-
};
|
|
235
|
-
writeDraft(filePath, header, draftBody({ provider, severity, record, fingerprint }));
|
|
236
|
-
const intentRef = relativeIntentRef(config.repoRoot, filePath);
|
|
237
|
-
ledger.append({
|
|
238
|
-
type: "INTENT_DRAFTED",
|
|
239
|
-
actor: KERNEL_ACTOR,
|
|
240
|
-
data: { intentRef, provider: provider.id, severity, fingerprint },
|
|
241
|
-
...OBSERVE_IDS,
|
|
242
|
-
});
|
|
243
|
-
return { outcome: "drafted", intentRef };
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function relativeIntentRef(repoRoot, filePath) {
|
|
247
|
-
return filePath.startsWith(repoRoot) ? filePath.slice(repoRoot.length + 1) : filePath;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export function runObserveCycle({ configPath, now = new Date().toISOString() }) {
|
|
251
|
-
const config = loadObserveConfig(configPath);
|
|
252
|
-
const ledger = openObserveLedger(config.repoRoot);
|
|
253
|
-
const cycle = ledger.state.cycles + 1;
|
|
254
|
-
ledger.append({
|
|
255
|
-
type: "OBSERVE_CYCLE_STARTED",
|
|
256
|
-
actor: KERNEL_ACTOR,
|
|
257
|
-
data: { cycle, configDigest: config.configDigest },
|
|
258
|
-
...OBSERVE_IDS,
|
|
259
|
-
});
|
|
260
|
-
const results = [];
|
|
261
|
-
for (const provider of config.providers) {
|
|
262
|
-
const execResult = execCommand({
|
|
263
|
-
name: `provider:${provider.id}`,
|
|
264
|
-
command: provider.command,
|
|
265
|
-
args: provider.args,
|
|
266
|
-
timeoutMs: provider.timeoutMs ?? 600000,
|
|
267
|
-
repoRoot: config.repoRoot,
|
|
268
|
-
input: { provider: provider.id, cycle },
|
|
269
|
-
});
|
|
270
|
-
const record = recordEvidence({
|
|
271
|
-
ledger,
|
|
272
|
-
config,
|
|
273
|
-
cycle,
|
|
274
|
-
provider,
|
|
275
|
-
execResult,
|
|
276
|
-
kind: provider.evidence.kind,
|
|
277
|
-
step: provider.id,
|
|
278
|
-
});
|
|
279
|
-
const severity = severityFor(provider, record.status);
|
|
280
|
-
const result = {
|
|
281
|
-
provider: provider.id,
|
|
282
|
-
status: record.status,
|
|
283
|
-
severity,
|
|
284
|
-
bands: [],
|
|
285
|
-
intent: null,
|
|
286
|
-
};
|
|
287
|
-
if (severity !== null) {
|
|
288
|
-
const fingerprint = fingerprintFor(provider, record.status);
|
|
289
|
-
for (const band of config.bands) {
|
|
290
|
-
if (severityRank(severity) < severityRank(band.minSeverity)) {
|
|
291
|
-
continue;
|
|
292
|
-
}
|
|
293
|
-
ledger.append({
|
|
294
|
-
type: "BAND_TRIGGERED",
|
|
295
|
-
actor: KERNEL_ACTOR,
|
|
296
|
-
data: { provider: provider.id, band: band.level, severity, fingerprint },
|
|
297
|
-
...OBSERVE_IDS,
|
|
298
|
-
});
|
|
299
|
-
result.bands.push(band.level);
|
|
300
|
-
if (band.level === "diagnose") {
|
|
301
|
-
const diagnoseResult = provider.diagnose
|
|
302
|
-
? execCommand({
|
|
303
|
-
name: `diagnose:${provider.id}`,
|
|
304
|
-
command: provider.diagnose.command,
|
|
305
|
-
args: provider.diagnose.args,
|
|
306
|
-
timeoutMs: provider.diagnose.timeoutMs ?? 600000,
|
|
307
|
-
repoRoot: config.repoRoot,
|
|
308
|
-
input: { provider: provider.id, cycle, band: "diagnose" },
|
|
309
|
-
})
|
|
310
|
-
: {
|
|
311
|
-
adapter: "observe",
|
|
312
|
-
command: "(no diagnose command configured)",
|
|
313
|
-
exitCode: null,
|
|
314
|
-
signal: null,
|
|
315
|
-
stdout: "",
|
|
316
|
-
stderr: "diagnose band triggered but the provider configures no diagnose command",
|
|
317
|
-
timedOut: false,
|
|
318
|
-
spawnError: "no diagnose command configured",
|
|
319
|
-
startedAt: now,
|
|
320
|
-
finishedAt: now,
|
|
321
|
-
};
|
|
322
|
-
recordEvidence({
|
|
323
|
-
ledger,
|
|
324
|
-
config,
|
|
325
|
-
cycle,
|
|
326
|
-
provider,
|
|
327
|
-
execResult: diagnoseResult,
|
|
328
|
-
kind: "diagnosis",
|
|
329
|
-
step: `${provider.id}-diagnose`,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
if (band.level === "intent") {
|
|
333
|
-
result.intent = handleIntentBand({
|
|
334
|
-
ledger,
|
|
335
|
-
config,
|
|
336
|
-
cycle,
|
|
337
|
-
provider,
|
|
338
|
-
severity,
|
|
339
|
-
record,
|
|
340
|
-
fingerprint,
|
|
341
|
-
now,
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
results.push(result);
|
|
347
|
-
}
|
|
348
|
-
ledger.append({
|
|
349
|
-
type: "OBSERVE_CYCLE_FINISHED",
|
|
350
|
-
actor: KERNEL_ACTOR,
|
|
351
|
-
data: { cycle, providersRun: config.providers.length },
|
|
352
|
-
...OBSERVE_IDS,
|
|
353
|
-
});
|
|
354
|
-
return {
|
|
355
|
-
cycle,
|
|
356
|
-
ledgerPath: ledger.path,
|
|
357
|
-
ledgerRef: toRepoRef(config.repoRoot, ledger.path),
|
|
358
|
-
results,
|
|
359
|
-
state: ledger.state,
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
export function triageIntent({ repoRoot, intentRef, action, by = "unknown", note, now = new Date().toISOString() }) {
|
|
364
|
-
if (!TRIAGE_ACTIONS.includes(action)) {
|
|
365
|
-
throw new ObserveError(`action must be one of ${TRIAGE_ACTIONS.join("|")}, got: ${action}`);
|
|
366
|
-
}
|
|
367
|
-
const root = resolve(repoRoot);
|
|
368
|
-
const candidatePaths = [
|
|
369
|
-
resolve(root, intentRef),
|
|
370
|
-
join(intentsDir(root), intentRef),
|
|
371
|
-
join(intentsDir(root), `${intentRef}.md`),
|
|
372
|
-
];
|
|
373
|
-
const filePath = candidatePaths.find((path) => existsSync(path));
|
|
374
|
-
if (!filePath) {
|
|
375
|
-
throw new ObserveError(`intent draft not found: ${intentRef}`);
|
|
376
|
-
}
|
|
377
|
-
const text = readFileSync(filePath, "utf8");
|
|
378
|
-
const header = parseIntentHeader(filePath, text);
|
|
379
|
-
const nextStatus = action === "fix_now" ? "accepted" : action === "schedule" ? "scheduled" : "dismissed";
|
|
380
|
-
const nextHeader = {
|
|
381
|
-
...header,
|
|
382
|
-
status: nextStatus,
|
|
383
|
-
updated: now,
|
|
384
|
-
triage: { action, by, at: now, ...(note ? { note } : {}) },
|
|
385
|
-
...(action === "dismiss" ? { dismissedSeverity: header.severity } : {}),
|
|
386
|
-
};
|
|
387
|
-
if (action !== "dismiss") {
|
|
388
|
-
delete nextHeader.dismissedSeverity;
|
|
389
|
-
}
|
|
390
|
-
rewriteHeader({ path: filePath, text }, nextHeader);
|
|
391
|
-
const ledger = openObserveLedger(root);
|
|
392
|
-
ledger.append({
|
|
393
|
-
type: "TRIAGE_RECORDED",
|
|
394
|
-
actor: { kind: "human", id: by },
|
|
395
|
-
data: {
|
|
396
|
-
intentRef: relativeIntentRef(root, filePath),
|
|
397
|
-
action,
|
|
398
|
-
fingerprint: header.fingerprint,
|
|
399
|
-
},
|
|
400
|
-
...OBSERVE_IDS,
|
|
401
|
-
});
|
|
402
|
-
return {
|
|
403
|
-
intentRef: relativeIntentRef(root, filePath),
|
|
404
|
-
status: nextStatus,
|
|
405
|
-
fingerprint: header.fingerprint,
|
|
406
|
-
suggestion:
|
|
407
|
-
action === "fix_now"
|
|
408
|
-
? "accepted: start a software-delivery run for this intent (run.js start --config <run-config.yaml>) — the draft never executes itself"
|
|
409
|
-
: null,
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
export function observeStatus({ repoRoot }) {
|
|
414
|
-
const root = resolve(repoRoot);
|
|
415
|
-
const ledger = openObserveLedger(root);
|
|
416
|
-
const drafts = readIntentDrafts(root);
|
|
417
|
-
return {
|
|
418
|
-
ledgerPath: ledger.path,
|
|
419
|
-
corruption: ledger.corruption,
|
|
420
|
-
cycles: ledger.state.cycles,
|
|
421
|
-
providers: ledger.state.providers,
|
|
422
|
-
intents: drafts.map((draft) => ({
|
|
423
|
-
file: relativeIntentRef(root, draft.path),
|
|
424
|
-
...draft.header,
|
|
425
|
-
})),
|
|
426
|
-
};
|
|
427
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# observe workflow preset per docs/v2/RFC-0003-workflow-policy.md §8 (frozen
|
|
2
|
-
# shape; block form — the strict YAML subset has no inline {}/[]).
|
|
3
|
-
# One `observe run` invocation = one cycle; scheduling is host-level in v0
|
|
4
|
-
# (cron/CI invoking the CLI). Copy to <repo>/.buildbeat/observe.yaml and
|
|
5
|
-
# point commands at the project's real probes.
|
|
6
|
-
kind: workflow
|
|
7
|
-
version: 1
|
|
8
|
-
name: observe
|
|
9
|
-
providers:
|
|
10
|
-
- id: drift-check
|
|
11
|
-
command: bash
|
|
12
|
-
args:
|
|
13
|
-
- scripts/drift-check.sh
|
|
14
|
-
schedule: manual
|
|
15
|
-
evidence:
|
|
16
|
-
kind: drift
|
|
17
|
-
subject: deploy-unit
|
|
18
|
-
severity:
|
|
19
|
-
failed: error
|
|
20
|
-
unverified: warn
|
|
21
|
-
diagnose:
|
|
22
|
-
command: bash
|
|
23
|
-
args:
|
|
24
|
-
- scripts/live-status.sh
|
|
25
|
-
- id: live-status
|
|
26
|
-
command: bash
|
|
27
|
-
args:
|
|
28
|
-
- scripts/live-status.sh
|
|
29
|
-
schedule: manual
|
|
30
|
-
evidence:
|
|
31
|
-
kind: runtime-health
|
|
32
|
-
subject: deploy-unit
|
|
33
|
-
bands:
|
|
34
|
-
- level: log
|
|
35
|
-
when: "severity >= info"
|
|
36
|
-
- level: diagnose
|
|
37
|
-
when: "severity >= error"
|
|
38
|
-
- level: intent
|
|
39
|
-
when: "severity >= error"
|
|
40
|
-
triage:
|
|
41
|
-
actions:
|
|
42
|
-
- fix_now
|
|
43
|
-
- schedule
|
|
44
|
-
- dismiss
|
|
45
|
-
dismissFeedback: bands
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# Invariant 22 (lessons.md「静态稿拍板 → 返工螺旋」, machine form): a UI delivery's spec approval
|
|
2
|
-
# subject must include renderable proof — a screenshot with a digest —
|
|
3
|
-
# before a human may stamp it. Static prose is not an approvable design.
|
|
4
|
-
kind: policy
|
|
5
|
-
version: 1
|
|
6
|
-
name: ui-render-gate
|
|
7
|
-
type: transition
|
|
8
|
-
appliesTo: enter-spec
|
|
9
|
-
enforcement: LOCAL_ENFORCED
|
|
10
|
-
rule:
|
|
11
|
-
evidence.exists:
|
|
12
|
-
kind: screenshot
|
|
13
|
-
minGrade: L2
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# Release readback lane (iteration 08, C8). The kernel still has no deploy
|
|
2
|
-
# capability (invariant 20): every production action stays a human's. What
|
|
3
|
-
# this lane fixes is the bookkeeping around it — a pilot go-live was
|
|
4
|
-
# ledgered by hand as forty "step N readback" commits. Here each step is a
|
|
5
|
-
# read-only command that proves the world is in the expected state; every
|
|
6
|
-
# failure stops for a human (maxAttempts 1, no fix edge), and evidence is
|
|
7
|
-
# graded L4 because it was read from the real environment.
|
|
8
|
-
#
|
|
9
|
-
# Shape: preflight (before the action) → [human performs the action; the
|
|
10
|
-
# risk preset "release" stops here] → apply-readback (proves it happened) →
|
|
11
|
-
# observe (proves it is healthy) → wait-close (human closes the window).
|
|
12
|
-
kind: workflow
|
|
13
|
-
version: 1
|
|
14
|
-
name: release-readback
|
|
15
|
-
entry: preflight
|
|
16
|
-
steps:
|
|
17
|
-
- id: preflight
|
|
18
|
-
worker: readback
|
|
19
|
-
readonly: true
|
|
20
|
-
grade: L4
|
|
21
|
-
- id: apply-readback
|
|
22
|
-
worker: readback
|
|
23
|
-
readonly: true
|
|
24
|
-
grade: L4
|
|
25
|
-
- id: observe
|
|
26
|
-
worker: observe
|
|
27
|
-
readonly: true
|
|
28
|
-
grade: L4
|
|
29
|
-
- id: wait-close
|
|
30
|
-
terminal:
|
|
31
|
-
- wait-close
|
|
32
|
-
budgets:
|
|
33
|
-
maxAttempts:
|
|
34
|
-
preflight: 1
|
|
35
|
-
apply-readback: 1
|
|
36
|
-
observe: 1
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Controlled lane: intent AND plan must be accepted artifacts before build;
|
|
2
|
-
# merge stays human behind a stricter evidence floor. The release gate from
|
|
3
|
-
# the controlled track is out of MVP scope (no deploy capability exists) and
|
|
4
|
-
# returns with observe/release work in M5.
|
|
5
|
-
kind: risk-preset
|
|
6
|
-
version: 1
|
|
7
|
-
name: controlled
|
|
8
|
-
policies:
|
|
9
|
-
- kind: policy
|
|
10
|
-
version: 1
|
|
11
|
-
name: intent-accepted
|
|
12
|
-
type: pre
|
|
13
|
-
appliesTo: build
|
|
14
|
-
enforcement: LOCAL_ENFORCED
|
|
15
|
-
rule:
|
|
16
|
-
artifact.accepted:
|
|
17
|
-
artifact: intent
|
|
18
|
-
- kind: policy
|
|
19
|
-
version: 1
|
|
20
|
-
name: plan-accepted
|
|
21
|
-
type: pre
|
|
22
|
-
appliesTo: build
|
|
23
|
-
enforcement: LOCAL_ENFORCED
|
|
24
|
-
rule:
|
|
25
|
-
artifact.accepted:
|
|
26
|
-
artifact: plan
|
|
27
|
-
- kind: policy
|
|
28
|
-
version: 1
|
|
29
|
-
name: merge-evidence-floor
|
|
30
|
-
type: transition
|
|
31
|
-
appliesTo: enter-wait-merge
|
|
32
|
-
enforcement: LOCAL_ENFORCED
|
|
33
|
-
rule:
|
|
34
|
-
all:
|
|
35
|
-
- evidence.exists:
|
|
36
|
-
kind: command
|
|
37
|
-
minGrade: L2
|
|
38
|
-
- finding.maxSeverity:
|
|
39
|
-
atMost: P3
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# Fast lane: automatic plan/build/verify/review, human merge decision only.
|
|
2
|
-
# The merge gate still demands read-back evidence and a clean review.
|
|
3
|
-
kind: risk-preset
|
|
4
|
-
version: 1
|
|
5
|
-
name: fast
|
|
6
|
-
policies:
|
|
7
|
-
- kind: policy
|
|
8
|
-
version: 1
|
|
9
|
-
name: merge-evidence-floor
|
|
10
|
-
type: transition
|
|
11
|
-
appliesTo: enter-wait-merge
|
|
12
|
-
enforcement: LOCAL_ENFORCED
|
|
13
|
-
rule:
|
|
14
|
-
all:
|
|
15
|
-
- evidence.exists:
|
|
16
|
-
kind: command
|
|
17
|
-
minGrade: L2
|
|
18
|
-
- finding.maxSeverity:
|
|
19
|
-
atMost: P2
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Release lane (iteration 08, C8): pairs with the release-readback workflow.
|
|
2
|
-
# Automation stops before apply-readback — that pause is where the human
|
|
3
|
-
# performs the production action — and the window may only close behind L4
|
|
4
|
-
# evidence read from the real environment. (No finding floor: the lane has
|
|
5
|
-
# no reviewer step, and a rule that cannot be satisfied is not a gate.)
|
|
6
|
-
kind: risk-preset
|
|
7
|
-
version: 1
|
|
8
|
-
name: release
|
|
9
|
-
stopAt:
|
|
10
|
-
- apply-readback
|
|
11
|
-
policies:
|
|
12
|
-
- kind: policy
|
|
13
|
-
version: 1
|
|
14
|
-
name: close-evidence-floor
|
|
15
|
-
type: transition
|
|
16
|
-
appliesTo: enter-wait-close
|
|
17
|
-
enforcement: LOCAL_ENFORCED
|
|
18
|
-
rule:
|
|
19
|
-
evidence.exists:
|
|
20
|
-
kind: command
|
|
21
|
-
minGrade: L4
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Standard lane: the plan must be an accepted artifact (digest-bound) before
|
|
2
|
-
# build starts; merge stays a human decision behind the evidence floor.
|
|
3
|
-
kind: risk-preset
|
|
4
|
-
version: 1
|
|
5
|
-
name: standard
|
|
6
|
-
policies:
|
|
7
|
-
- kind: policy
|
|
8
|
-
version: 1
|
|
9
|
-
name: plan-accepted
|
|
10
|
-
type: pre
|
|
11
|
-
appliesTo: build
|
|
12
|
-
enforcement: LOCAL_ENFORCED
|
|
13
|
-
rule:
|
|
14
|
-
artifact.accepted:
|
|
15
|
-
artifact: plan
|
|
16
|
-
- kind: policy
|
|
17
|
-
version: 1
|
|
18
|
-
name: merge-evidence-floor
|
|
19
|
-
type: transition
|
|
20
|
-
appliesTo: enter-wait-merge
|
|
21
|
-
enforcement: LOCAL_ENFORCED
|
|
22
|
-
rule:
|
|
23
|
-
all:
|
|
24
|
-
- evidence.exists:
|
|
25
|
-
kind: command
|
|
26
|
-
minGrade: L2
|
|
27
|
-
- finding.maxSeverity:
|
|
28
|
-
atMost: P2
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# ARCHITECTURE.md — <项目名> 全栈总图(AI 会话接手按需读这份)
|
|
2
|
-
|
|
3
|
-
> 一句话:<项目是什么、给谁用、核心取舍>。
|
|
4
|
-
> 本文是**顶层总图**:架构链路 + 文件夹 + 基础设施标识。**按需读,不自动装载**(自动装载的是根 `AGENTS.md`)。各子系统细节在各代码子仓自己的 `AGENTS.md`/`README.md`,文末有索引。
|
|
5
|
-
> 🔴 凭据(key / DB 密码 / token)一律**只标位置、不写值**。任何会话不得把凭据写进任何文档或代码。
|
|
6
|
-
|
|
7
|
-
## 0. 架构链路
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
<用户入口>
|
|
11
|
-
▼
|
|
12
|
-
<前端/客户端>(<仓名>,<部署单元>)
|
|
13
|
-
▼
|
|
14
|
-
<网关/服务A> ──▶ <审计/可观测>
|
|
15
|
-
├──▶ <服务B>
|
|
16
|
-
└──▶ <服务C>
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## 1. 文件夹
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
<项目根>/
|
|
23
|
-
├── AGENTS.md / ARCHITECTURE.md / 指挥台.md / BUILDBEAT.md / contracts/ / delivery/ / pm/decisions.md
|
|
24
|
-
├── <代码仓1>/ # ★ <说明>;详见其 AGENTS.md
|
|
25
|
-
└── <代码仓2>/ # ★ <说明>
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## 2. 基础设施标识(资源变动时更新本节;细节别往这塞)
|
|
29
|
-
|
|
30
|
-
| 项 | 值 | 说明 |
|
|
31
|
-
|---|---|---|
|
|
32
|
-
| <部署平台/区域> | | |
|
|
33
|
-
| <应用/服务清单:名称+ID+实例> | | |
|
|
34
|
-
| <入口/域名/LB> | | |
|
|
35
|
-
| <数据库/缓存> | | |
|
|
36
|
-
|
|
37
|
-
### 凭据位置(🔴 只读取,不外泄、不写值)
|
|
38
|
-
- <服务A 凭据> → `<路径/获取命令>`
|
|
39
|
-
- <运行时 env 实查> → `<平台 CLI 命令>`
|
|
40
|
-
|
|
41
|
-
## 3. 红线
|
|
42
|
-
见根 `AGENTS.md §3`(凭据/不 add -A/不未授权部署/CHANGELOG/写者≠审者)。
|
|
43
|
-
|
|
44
|
-
## 4. 子项目文档索引(改哪块先读哪份)
|
|
45
|
-
|
|
46
|
-
| 改动范围 | 先读 |
|
|
47
|
-
|---|---|
|
|
48
|
-
| <代码仓1> | `<代码仓1>/AGENTS.md` + `CHANGELOG.md` |
|
|
49
|
-
| <代码仓2> | `<代码仓2>/README.md` |
|
|
50
|
-
| 跨边界契约 | `contracts/PROTOCOL.md` |
|