intentdna 1.6.5 → 1.7.4
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +26 -15
- package/dist/cli/commands/compile.js +2 -47
- package/dist/cli/commands/context.d.ts +8 -0
- package/dist/cli/commands/context.js +63 -0
- package/dist/cli/commands/feedback.d.ts +3 -2
- package/dist/cli/commands/feedback.js +16 -5
- package/dist/cli/commands/init.js +11 -63
- package/dist/cli/commands/run.js +5 -4
- package/dist/cli/commands/show.js +2 -38
- package/dist/cli/commands/sync.d.ts +9 -6
- package/dist/cli/commands/sync.js +191 -186
- package/dist/cli/commands/templates.d.ts +10 -1
- package/dist/cli/commands/templates.js +50 -1
- package/dist/cli/commands/validate.js +15 -9
- package/dist/cli/commands/verify.d.ts +32 -0
- package/dist/cli/commands/verify.js +270 -31
- package/dist/cli/index.js +78 -11
- package/dist/compiler/activate.js +11 -6
- package/dist/compiler/cascade.d.ts +5 -1
- package/dist/compiler/cascade.js +74 -1
- package/dist/compiler/compile.js +38 -0
- package/dist/compiler/diagnostics.d.ts +17 -0
- package/dist/compiler/diagnostics.js +30 -0
- package/dist/compiler/index.d.ts +7 -0
- package/dist/compiler/index.js +13 -13
- package/dist/compiler/input-resolver.d.ts +32 -0
- package/dist/compiler/input-resolver.js +281 -0
- package/dist/compiler/provenance.d.ts +8 -0
- package/dist/compiler/provenance.js +127 -0
- package/dist/governance/index.d.ts +4 -3
- package/dist/governance/index.js +3 -4
- package/dist/governance/runtime-decision-event.d.ts +85 -0
- package/dist/governance/runtime-decision-event.js +231 -0
- package/dist/governance/types.d.ts +3 -3
- package/dist/governance/types.js +3 -3
- package/dist/hooks/cli.d.ts +10 -1
- package/dist/hooks/cli.js +199 -35
- package/dist/hooks/state.d.ts +5 -10
- package/dist/hooks/state.js +170 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/tools-compile.js +18 -49
- package/dist/mcp/tools-context.d.ts +2 -0
- package/dist/mcp/tools-context.js +85 -0
- package/dist/mcp/tools-enforce.d.ts +2 -2
- package/dist/mcp/tools-enforce.js +19 -49
- package/dist/mcp/tools-observability.js +26 -2
- package/dist/mcp/tools-state.d.ts +1 -1
- package/dist/mcp/tools-state.js +17 -7
- package/dist/report/kernel-report.d.ts +27 -0
- package/dist/report/kernel-report.js +60 -19
- package/dist/report/kernel-signals.d.ts +5 -2
- package/dist/report/kernel-signals.js +87 -2
- package/dist/report/report-package.d.ts +64 -0
- package/dist/report/report-package.js +90 -0
- package/dist/runtime/agent-md.d.ts +1 -0
- package/dist/runtime/agent-md.js +21 -3
- package/dist/runtime/claude-sdk.d.ts +9 -4
- package/dist/runtime/claude-sdk.js +9 -0
- package/dist/runtime/context-sources.d.ts +14 -0
- package/dist/runtime/context-sources.js +60 -0
- package/dist/runtime/plugin-adapter.d.ts +5 -1
- package/dist/runtime/plugin-adapter.js +2 -0
- package/dist/runtime/skill-adapter.d.ts +32 -4
- package/dist/runtime/skill-adapter.js +184 -9
- package/dist/runtime/workflow-runner.d.ts +1 -1
- package/dist/runtime/workflow-runner.js +1 -1
- package/dist/schema/types.d.ts +84 -0
- package/dist/schema/validate.js +156 -2
- package/dist/schema/validators/controllers.js +16 -0
- package/dist/signals/index.d.ts +10 -0
- package/dist/signals/index.js +90 -5
- package/dist/templates/catalog.d.ts +19 -0
- package/dist/templates/catalog.js +57 -0
- package/dist/templates/flutter-rewrite.dna.yaml +2 -2
- package/package.json +1 -1
- package/spec/README.md +1 -1
- package/spec/foundation-hardening.md +2 -1
- package/spec/schema-spec.md +78 -1
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
export const RUNTIME_DECISION_EVENT_SCHEMA_VERSION = "intentdna.runtime_decision_event.v1.7.1";
|
|
2
|
+
export const CASCADE_LAYER_NAMES = [
|
|
3
|
+
"species",
|
|
4
|
+
"enterprise",
|
|
5
|
+
"project",
|
|
6
|
+
"personal",
|
|
7
|
+
"role",
|
|
8
|
+
"context",
|
|
9
|
+
"task",
|
|
10
|
+
];
|
|
11
|
+
const RUNTIME_DECISIONS = new Set(["allow", "warn", "block", "escalate", "validate"]);
|
|
12
|
+
const ENFORCEMENT_POINTS = new Set([
|
|
13
|
+
"hook",
|
|
14
|
+
"sdk",
|
|
15
|
+
"ci",
|
|
16
|
+
"plugin_runtime",
|
|
17
|
+
"mcp_external_write",
|
|
18
|
+
"unknown",
|
|
19
|
+
"unsupported",
|
|
20
|
+
]);
|
|
21
|
+
const CASCADE_LAYER_VALUES = new Set([...CASCADE_LAYER_NAMES, "unknown", "unsupported"]);
|
|
22
|
+
const CODON_TYPES = new Set(["attract", "repel", "threshold", "weight", "sense"]);
|
|
23
|
+
const EVIDENCE_REF_TYPES = new Set([
|
|
24
|
+
"trace",
|
|
25
|
+
"audit",
|
|
26
|
+
"artifact",
|
|
27
|
+
"verifier",
|
|
28
|
+
"handoff",
|
|
29
|
+
"legacy",
|
|
30
|
+
"unknown",
|
|
31
|
+
"unsupported",
|
|
32
|
+
]);
|
|
33
|
+
const REQUIRED_STRING_FIELDS = [
|
|
34
|
+
"schema_version",
|
|
35
|
+
"event_id",
|
|
36
|
+
"timestamp",
|
|
37
|
+
"org_id",
|
|
38
|
+
"team_id",
|
|
39
|
+
"user_id",
|
|
40
|
+
"project_id",
|
|
41
|
+
"agent_id",
|
|
42
|
+
"agent_role",
|
|
43
|
+
"agent_type",
|
|
44
|
+
"session_agent",
|
|
45
|
+
"harness_adapter",
|
|
46
|
+
"runtime",
|
|
47
|
+
"policy_bundle_id",
|
|
48
|
+
"policy_bundle_version",
|
|
49
|
+
"compiled_ir_hash",
|
|
50
|
+
"decision_reason",
|
|
51
|
+
"enforcement_point",
|
|
52
|
+
"session_id",
|
|
53
|
+
"run_id",
|
|
54
|
+
"step_id",
|
|
55
|
+
"tool_name",
|
|
56
|
+
"action_kind",
|
|
57
|
+
"resource_ref",
|
|
58
|
+
];
|
|
59
|
+
const REQUIRED_WINNING_CONSTRAINT_FIELDS = [
|
|
60
|
+
"constraint_id",
|
|
61
|
+
"source_layer",
|
|
62
|
+
"source_dna_id",
|
|
63
|
+
"gene_id",
|
|
64
|
+
"codon_type",
|
|
65
|
+
"provenance_reason",
|
|
66
|
+
];
|
|
67
|
+
const REQUIRED_HARNESS_CONTEXT_FIELDS = [
|
|
68
|
+
"agent_id",
|
|
69
|
+
"agent_role",
|
|
70
|
+
"agent_type",
|
|
71
|
+
"session_agent",
|
|
72
|
+
"harness_adapter",
|
|
73
|
+
"runtime",
|
|
74
|
+
];
|
|
75
|
+
const ENTERPRISE_EVIDENCE_FIELDS = [
|
|
76
|
+
...REQUIRED_STRING_FIELDS,
|
|
77
|
+
"decision",
|
|
78
|
+
];
|
|
79
|
+
const ENTERPRISE_WINNING_CONSTRAINT_FIELDS = [
|
|
80
|
+
...REQUIRED_WINNING_CONSTRAINT_FIELDS,
|
|
81
|
+
"action",
|
|
82
|
+
];
|
|
83
|
+
function isRecord(value) {
|
|
84
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
85
|
+
}
|
|
86
|
+
function hasString(record, field) {
|
|
87
|
+
return typeof record[field] === "string" && record[field].trim().length > 0;
|
|
88
|
+
}
|
|
89
|
+
function isPlaceholder(value) {
|
|
90
|
+
return value === "unknown" || value === "unsupported";
|
|
91
|
+
}
|
|
92
|
+
function validateStringFields(record, fields, prefix, errors) {
|
|
93
|
+
for (const field of fields) {
|
|
94
|
+
if (!hasString(record, field))
|
|
95
|
+
errors.push(`${prefix}${field} must be a non-empty string`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function validateCascadeLayers(value, errors) {
|
|
99
|
+
if (!isRecord(value)) {
|
|
100
|
+
errors.push("cascade_layers must be an object with all cascade layer keys");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (const layer of CASCADE_LAYER_NAMES) {
|
|
104
|
+
const entry = value[layer];
|
|
105
|
+
if (!isRecord(entry)) {
|
|
106
|
+
errors.push(`cascade_layers.${layer} must be an object`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (entry.layer !== layer)
|
|
110
|
+
errors.push(`cascade_layers.${layer}.layer must be ${layer}`);
|
|
111
|
+
validateStringFields(entry, ["source_id", "source_ref", "fingerprint", "status"], `cascade_layers.${layer}.`, errors);
|
|
112
|
+
if (hasString(entry, "status") && entry.status !== "active" && entry.status !== "unknown" && entry.status !== "unsupported") {
|
|
113
|
+
errors.push(`cascade_layers.${layer}.status must be active, unknown, or unsupported`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function validateWinningConstraint(value, event, errors) {
|
|
118
|
+
if (!isRecord(value)) {
|
|
119
|
+
errors.push("winning_constraint must be an object");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
validateStringFields(value, REQUIRED_WINNING_CONSTRAINT_FIELDS, "winning_constraint.", errors);
|
|
123
|
+
if (hasString(value, "source_layer") && !CASCADE_LAYER_VALUES.has(value.source_layer)) {
|
|
124
|
+
errors.push("winning_constraint.source_layer must be a known cascade layer, unknown, or unsupported");
|
|
125
|
+
}
|
|
126
|
+
if (hasString(value, "codon_type") && !CODON_TYPES.has(value.codon_type)) {
|
|
127
|
+
errors.push("winning_constraint.codon_type must be a known codon type");
|
|
128
|
+
}
|
|
129
|
+
if (!RUNTIME_DECISIONS.has(value.action)) {
|
|
130
|
+
errors.push("winning_constraint.action must be allow, warn, block, escalate, or validate");
|
|
131
|
+
}
|
|
132
|
+
if (hasString(event, "decision") && value.action !== event.decision) {
|
|
133
|
+
errors.push("winning_constraint.action must match top-level decision");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function validateHarnessRuntimeContext(value, event, errors) {
|
|
137
|
+
if (!isRecord(value)) {
|
|
138
|
+
errors.push("harness_runtime_context must be an object");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
validateStringFields(value, REQUIRED_HARNESS_CONTEXT_FIELDS, "harness_runtime_context.", errors);
|
|
142
|
+
for (const field of REQUIRED_HARNESS_CONTEXT_FIELDS) {
|
|
143
|
+
if (hasString(event, field) && hasString(value, field) && event[field] !== value[field]) {
|
|
144
|
+
errors.push(`harness_runtime_context.${field} must match top-level ${field}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function validateEvidenceRefs(value, errors) {
|
|
149
|
+
if (!Array.isArray(value)) {
|
|
150
|
+
errors.push("evidence_refs must be an array");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (value.length === 0) {
|
|
154
|
+
errors.push("evidence_refs must contain at least one evidence reference");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
value.forEach((ref, index) => {
|
|
158
|
+
if (!isRecord(ref)) {
|
|
159
|
+
errors.push(`evidence_refs.${index} must be an object`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
validateStringFields(ref, ["type", "ref"], `evidence_refs.${index}.`, errors);
|
|
163
|
+
if (hasString(ref, "type") && !EVIDENCE_REF_TYPES.has(ref.type)) {
|
|
164
|
+
errors.push(`evidence_refs.${index}.type must be a known evidence reference type`);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function hasCompleteEnterpriseProvenance(event) {
|
|
169
|
+
if (!ENTERPRISE_EVIDENCE_FIELDS.every((field) => hasString(event, field) && !isPlaceholder(event[field])))
|
|
170
|
+
return false;
|
|
171
|
+
if (!isRecord(event.harness_runtime_context))
|
|
172
|
+
return false;
|
|
173
|
+
if (!REQUIRED_HARNESS_CONTEXT_FIELDS.every((field) => hasString(event.harness_runtime_context, field) && !isPlaceholder(event.harness_runtime_context[field])))
|
|
174
|
+
return false;
|
|
175
|
+
if (!isRecord(event.winning_constraint))
|
|
176
|
+
return false;
|
|
177
|
+
if (!ENTERPRISE_WINNING_CONSTRAINT_FIELDS.every((field) => hasString(event.winning_constraint, field) && !isPlaceholder(event.winning_constraint[field])))
|
|
178
|
+
return false;
|
|
179
|
+
if (!isRecord(event.cascade_layers))
|
|
180
|
+
return false;
|
|
181
|
+
for (const layer of CASCADE_LAYER_NAMES) {
|
|
182
|
+
const entry = event.cascade_layers[layer];
|
|
183
|
+
if (!isRecord(entry))
|
|
184
|
+
return false;
|
|
185
|
+
if (entry.status !== "active")
|
|
186
|
+
return false;
|
|
187
|
+
if (["source_id", "source_ref", "fingerprint"].some((field) => !hasString(entry, field) || isPlaceholder(entry[field])))
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (!Array.isArray(event.evidence_refs) || event.evidence_refs.length === 0)
|
|
191
|
+
return false;
|
|
192
|
+
for (const ref of event.evidence_refs) {
|
|
193
|
+
if (!isRecord(ref))
|
|
194
|
+
return false;
|
|
195
|
+
if (!hasString(ref, "type") || isPlaceholder(ref.type) || ref.type === "legacy")
|
|
196
|
+
return false;
|
|
197
|
+
if (!hasString(ref, "ref") || isPlaceholder(ref.ref))
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
export function validateRuntimeDecisionEvent(event) {
|
|
203
|
+
const errors = [];
|
|
204
|
+
if (!isRecord(event)) {
|
|
205
|
+
return {
|
|
206
|
+
valid: false,
|
|
207
|
+
classification: "legacy_diagnostic",
|
|
208
|
+
errors: ["RuntimeDecisionEvent must be an object"],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
validateStringFields(event, REQUIRED_STRING_FIELDS, "", errors);
|
|
212
|
+
if (event.schema_version !== RUNTIME_DECISION_EVENT_SCHEMA_VERSION) {
|
|
213
|
+
errors.push(`schema_version must be ${RUNTIME_DECISION_EVENT_SCHEMA_VERSION}`);
|
|
214
|
+
}
|
|
215
|
+
if (!RUNTIME_DECISIONS.has(event.decision)) {
|
|
216
|
+
errors.push("decision must be allow, warn, block, escalate, or validate");
|
|
217
|
+
}
|
|
218
|
+
if (!ENFORCEMENT_POINTS.has(event.enforcement_point)) {
|
|
219
|
+
errors.push("enforcement_point must identify hook, sdk, ci, plugin runtime, MCP external write, unknown, or unsupported");
|
|
220
|
+
}
|
|
221
|
+
validateCascadeLayers(event.cascade_layers, errors);
|
|
222
|
+
validateWinningConstraint(event.winning_constraint, event, errors);
|
|
223
|
+
validateHarnessRuntimeContext(event.harness_runtime_context, event, errors);
|
|
224
|
+
validateEvidenceRefs(event.evidence_refs, errors);
|
|
225
|
+
const valid = errors.length === 0;
|
|
226
|
+
return {
|
|
227
|
+
valid,
|
|
228
|
+
classification: valid && hasCompleteEnterpriseProvenance(event) ? "enterprise_evidence" : "legacy_diagnostic",
|
|
229
|
+
errors,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Intent DNA — Remote Governance Types
|
|
3
3
|
*
|
|
4
|
-
* Architecture reservation for enterprise governance communication.
|
|
5
|
-
*
|
|
4
|
+
* Architecture reservation for future remote enterprise governance communication.
|
|
5
|
+
* Current Enterprise Safety Slice uses local/shared policy bundles and RuntimeDecisionEvent evidence.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Reserved capabilities:
|
|
8
8
|
* - Audit data reporting to central server
|
|
9
9
|
* - Enterprise DNA policy pull
|
|
10
10
|
* - Policy update push notifications
|
package/dist/governance/types.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Intent DNA — Remote Governance Types
|
|
3
3
|
*
|
|
4
|
-
* Architecture reservation for enterprise governance communication.
|
|
5
|
-
*
|
|
4
|
+
* Architecture reservation for future remote enterprise governance communication.
|
|
5
|
+
* Current Enterprise Safety Slice uses local/shared policy bundles and RuntimeDecisionEvent evidence.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Reserved capabilities:
|
|
8
8
|
* - Audit data reporting to central server
|
|
9
9
|
* - Enterprise DNA policy pull
|
|
10
10
|
* - Policy update push notifications
|
package/dist/hooks/cli.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Fail-open: all errors → { continue: true, suppressOutput: true }
|
|
16
16
|
*/
|
|
17
|
-
import type { ArtifactFact, ConstraintIR, VerifierSpec } from "../schema/types.js";
|
|
17
|
+
import type { ArtifactFact, ConstraintIR, RoleDef, VerifierSpec } from "../schema/types.js";
|
|
18
18
|
import type { HookEvent, HookOutput } from "./protocol.js";
|
|
19
19
|
import { blockOutput } from "./protocol.js";
|
|
20
20
|
import { readWorkflowState } from "./state.js";
|
|
@@ -39,6 +39,15 @@ export declare function computeSummary(traces: Array<{
|
|
|
39
39
|
reason?: string;
|
|
40
40
|
}>): SessionSummary;
|
|
41
41
|
export declare function formatSummary(s: SessionSummary): string | null;
|
|
42
|
+
export interface RunHookEventOptions {
|
|
43
|
+
event: HookEvent;
|
|
44
|
+
ir: ConstraintIR;
|
|
45
|
+
rawInput: Record<string, unknown>;
|
|
46
|
+
projectDir: string;
|
|
47
|
+
sessionId?: string;
|
|
48
|
+
roles?: Record<string, RoleDef>;
|
|
49
|
+
}
|
|
50
|
+
export declare function runHookEvent(options: RunHookEventOptions): Promise<HookOutput>;
|
|
42
51
|
declare function recordProducedArtifacts(projectDir: string, ir: ConstraintIR, wfState: {
|
|
43
52
|
workflow: string;
|
|
44
53
|
current_step: string;
|
package/dist/hooks/cli.js
CHANGED
|
@@ -15,18 +15,141 @@
|
|
|
15
15
|
* Fail-open: all errors → { continue: true, suppressOutput: true }
|
|
16
16
|
*/
|
|
17
17
|
import { mkdir, readFile, realpath, stat, unlink, writeFile } from "node:fs/promises";
|
|
18
|
+
import { realpathSync } from "node:fs";
|
|
18
19
|
import { spawn } from "node:child_process";
|
|
19
20
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
20
|
-
import { randomUUID } from "node:crypto";
|
|
21
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
21
23
|
import { readStdin, writeOutput, silentOutput, allowOutput, blockOutput, stopOutput } from "./protocol.js";
|
|
22
24
|
import { validateHookInput } from "./schema.js";
|
|
23
25
|
import { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, extractBashWritePaths, checkReflectionLimit, checkContextReadiness, checkWorkflowBoundary, } from "./enforce.js";
|
|
24
|
-
import { appendAudit, readWorkflowState, appendTrace, rotateTraces, readTraces, cleanStaleState, readSurgeonAttempts, writeSurgeonAttempts, appendSessionRead, readSessionReads, appendVerifierResult, appendCompletedArtifact, artifactIdentity, buildArtifactKey, readArtifactManifest, resolveArtifactTemplate, safePathComponent, writeArtifactManifest } from "./state.js";
|
|
26
|
+
import { appendAudit, readWorkflowState, appendTrace, appendRuntimeDecisionEvent, rotateTraces, readTraces, cleanStaleState, readSurgeonAttempts, writeSurgeonAttempts, appendSessionRead, readSessionReads, appendVerifierResult, appendCompletedArtifact, artifactIdentity, buildArtifactKey, readArtifactManifest, resolveArtifactTemplate, safePathComponent, writeArtifactManifest } from "./state.js";
|
|
25
27
|
import { hookEventsForSurface } from "./event-registry.js";
|
|
26
28
|
import { writeAuditEvent } from "../audit/index.js";
|
|
29
|
+
import { RUNTIME_DECISION_EVENT_SCHEMA_VERSION } from "../governance/index.js";
|
|
27
30
|
// ── Constants ──────────────────────────────────────────────
|
|
28
31
|
const DEFAULT_IR_PATH = ".dna/compiled/ir.json";
|
|
29
32
|
const VALID_EVENTS = new Set(hookEventsForSurface("cli"));
|
|
33
|
+
// ── Runtime Decision Evidence ────────────────────────────
|
|
34
|
+
function normalizeDecision(output) {
|
|
35
|
+
if (output.continue === false)
|
|
36
|
+
return "block";
|
|
37
|
+
if (output.hookSpecificOutput?.permissionDecision === "ask")
|
|
38
|
+
return "escalate";
|
|
39
|
+
if (output.hookSpecificOutput?.additionalContext?.startsWith("WARN"))
|
|
40
|
+
return "warn";
|
|
41
|
+
return "allow";
|
|
42
|
+
}
|
|
43
|
+
function hashedRef(value) {
|
|
44
|
+
const raw = typeof value === "string" && value.length > 0 ? value : "unknown";
|
|
45
|
+
return `sha256:${createHash("sha256").update(raw, "utf-8").digest("hex")}`;
|
|
46
|
+
}
|
|
47
|
+
function resourceRef(rawInput) {
|
|
48
|
+
const toolInput = typeof rawInput.tool_input === "object" && rawInput.tool_input !== null
|
|
49
|
+
? rawInput.tool_input
|
|
50
|
+
: {};
|
|
51
|
+
return hashedRef(toolInput.file_path ?? toolInput.notebook_path ?? toolInput.command ?? rawInput.message ?? rawInput.prompt ?? "unknown");
|
|
52
|
+
}
|
|
53
|
+
function actionKind(rawInput) {
|
|
54
|
+
const toolName = typeof rawInput.tool_name === "string" ? rawInput.tool_name : "unknown";
|
|
55
|
+
if (["Edit", "Write", "NotebookEdit"].includes(toolName))
|
|
56
|
+
return "file_write";
|
|
57
|
+
if (toolName === "Bash")
|
|
58
|
+
return "shell_command";
|
|
59
|
+
return toolName === "unknown" ? "unknown" : "tool_use";
|
|
60
|
+
}
|
|
61
|
+
function cascadeLayers(ir) {
|
|
62
|
+
const layers = ir.provenance?.cascade_layers;
|
|
63
|
+
const result = {};
|
|
64
|
+
for (const layer of ["species", "enterprise", "project", "personal", "role", "context", "task"]) {
|
|
65
|
+
const entry = layers?.[layer];
|
|
66
|
+
result[layer] = {
|
|
67
|
+
layer,
|
|
68
|
+
source_id: entry?.source_id ?? "unknown",
|
|
69
|
+
source_ref: entry?.source_ref ?? "unknown",
|
|
70
|
+
fingerprint: entry?.fingerprint ?? "unknown",
|
|
71
|
+
status: entry?.status ?? "unknown",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
function fallbackConstraint(decision, reason, ir) {
|
|
77
|
+
return {
|
|
78
|
+
constraint_id: decision === "allow" ? "allow:no_blocking_constraint" : "unknown",
|
|
79
|
+
source_layer: decision === "allow" && ir?.provenance?.policy_bundle_id ? "enterprise" : "unknown",
|
|
80
|
+
source_dna_id: decision === "allow" ? ir?.provenance?.policy_bundle_id ?? "unknown" : "unknown",
|
|
81
|
+
gene_id: decision === "allow" ? "no_blocking_constraint" : "unknown",
|
|
82
|
+
codon_type: "threshold",
|
|
83
|
+
action: decision,
|
|
84
|
+
provenance_reason: reason || "unknown",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function candidateConstraintMatches(candidate, decision, result) {
|
|
88
|
+
if (candidate.action !== decision)
|
|
89
|
+
return false;
|
|
90
|
+
const geneId = result?.trace?.gene_id;
|
|
91
|
+
return !geneId || candidate.gene_id === geneId;
|
|
92
|
+
}
|
|
93
|
+
function winningConstraint(ir, decision, reason, result) {
|
|
94
|
+
const candidates = ir.provenance?.constraint_winners ?? [];
|
|
95
|
+
if (decision === "allow")
|
|
96
|
+
return fallbackConstraint(decision, reason, ir);
|
|
97
|
+
const matched = candidates.find((candidate) => candidateConstraintMatches(candidate, decision, result))
|
|
98
|
+
?? candidates.find((candidate) => candidate.action === decision);
|
|
99
|
+
if (!matched)
|
|
100
|
+
return fallbackConstraint(decision, reason, ir);
|
|
101
|
+
return { ...matched, action: decision };
|
|
102
|
+
}
|
|
103
|
+
function identityField(rawInput, inputKey, envKey) {
|
|
104
|
+
const value = rawInput[inputKey];
|
|
105
|
+
if (typeof value === "string" && value.trim().length > 0)
|
|
106
|
+
return value;
|
|
107
|
+
return process.env[envKey] ?? "unknown";
|
|
108
|
+
}
|
|
109
|
+
function buildRuntimeDecisionEvent(params) {
|
|
110
|
+
const agentType = typeof params.rawInput.agent_type === "string" ? params.rawInput.agent_type : "unknown";
|
|
111
|
+
const derivedRole = agentType.startsWith("dna-") ? agentType.slice("dna-".length) : agentType;
|
|
112
|
+
const agentRole = params.wfState?.current_role ?? params.result?.trace?.role ?? (derivedRole === "unknown" ? "unknown" : derivedRole);
|
|
113
|
+
const sessionId = params.sessionId ?? "unknown";
|
|
114
|
+
return {
|
|
115
|
+
schema_version: RUNTIME_DECISION_EVENT_SCHEMA_VERSION,
|
|
116
|
+
event_id: randomUUID(),
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
org_id: identityField(params.rawInput, "org_id", "INTENTDNA_ORG_ID"),
|
|
119
|
+
team_id: identityField(params.rawInput, "team_id", "INTENTDNA_TEAM_ID"),
|
|
120
|
+
user_id: identityField(params.rawInput, "user_id", "INTENTDNA_USER_ID"),
|
|
121
|
+
project_id: params.projectDir,
|
|
122
|
+
agent_id: agentType,
|
|
123
|
+
agent_role: agentRole,
|
|
124
|
+
agent_type: agentType,
|
|
125
|
+
session_agent: sessionId,
|
|
126
|
+
harness_adapter: "claude-code",
|
|
127
|
+
runtime: "hook",
|
|
128
|
+
policy_bundle_id: params.ir.provenance?.policy_bundle_id ?? "unknown",
|
|
129
|
+
policy_bundle_version: params.ir.provenance?.policy_bundle_version ?? "unknown",
|
|
130
|
+
cascade_layers: cascadeLayers(params.ir),
|
|
131
|
+
winning_constraint: winningConstraint(params.ir, params.decision, params.reason, params.result),
|
|
132
|
+
compiled_ir_hash: params.ir.compiled_ir_hash ?? params.ir.provenance?.compiled_ir_hash ?? "unknown",
|
|
133
|
+
decision: params.decision,
|
|
134
|
+
decision_reason: params.reason || "unknown",
|
|
135
|
+
enforcement_point: "hook",
|
|
136
|
+
harness_runtime_context: {
|
|
137
|
+
agent_id: agentType,
|
|
138
|
+
agent_role: agentRole,
|
|
139
|
+
agent_type: agentType,
|
|
140
|
+
session_agent: sessionId,
|
|
141
|
+
harness_adapter: "claude-code",
|
|
142
|
+
runtime: "hook",
|
|
143
|
+
},
|
|
144
|
+
session_id: sessionId,
|
|
145
|
+
run_id: params.wfState?.workflow ?? sessionId,
|
|
146
|
+
step_id: params.wfState?.current_step ?? params.result?.trace?.step_id ?? `${params.event}:direct`,
|
|
147
|
+
tool_name: typeof params.rawInput.tool_name === "string" ? params.rawInput.tool_name : "unknown",
|
|
148
|
+
action_kind: actionKind(params.rawInput),
|
|
149
|
+
resource_ref: resourceRef(params.rawInput),
|
|
150
|
+
evidence_refs: [{ type: "trace", ref: params.traceId }],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
30
153
|
export function computeSummary(traces) {
|
|
31
154
|
let blocks = 0, warns = 0;
|
|
32
155
|
const toolCounts = new Map();
|
|
@@ -101,14 +224,36 @@ async function main() {
|
|
|
101
224
|
writeOutput(silentOutput());
|
|
102
225
|
return;
|
|
103
226
|
}
|
|
227
|
+
const output = await runHookEvent({ event, ir, rawInput, projectDir, sessionId });
|
|
228
|
+
writeOutput(output);
|
|
229
|
+
}
|
|
230
|
+
export async function runHookEvent(options) {
|
|
231
|
+
const { event, ir, rawInput, projectDir, sessionId, roles } = options;
|
|
104
232
|
const state = {};
|
|
233
|
+
const writeRuntimeDecision = async (output, result, wfState, traceId = randomUUID()) => {
|
|
234
|
+
const decision = normalizeDecision(output);
|
|
235
|
+
const reason = decision !== "allow" ? (output.reason ?? output.hookSpecificOutput?.additionalContext ?? "unknown") : "allowed by active policy";
|
|
236
|
+
await appendRuntimeDecisionEvent(projectDir, buildRuntimeDecisionEvent({
|
|
237
|
+
ir,
|
|
238
|
+
rawInput,
|
|
239
|
+
output,
|
|
240
|
+
result,
|
|
241
|
+
event,
|
|
242
|
+
projectDir,
|
|
243
|
+
sessionId,
|
|
244
|
+
traceId,
|
|
245
|
+
decision,
|
|
246
|
+
reason,
|
|
247
|
+
wfState,
|
|
248
|
+
}), sessionId);
|
|
249
|
+
};
|
|
105
250
|
let wfStateRaw = null;
|
|
106
251
|
// Load workflow state for events that need it (handoff context + PreCompact preservation)
|
|
107
252
|
if (event === "PreToolUse" || event === "PostToolUse" || event === "PreCompact") {
|
|
108
253
|
const workflowState = await readWorkflowStateForHook(projectDir, event, sessionId);
|
|
109
254
|
if ("output" in workflowState) {
|
|
110
|
-
|
|
111
|
-
return;
|
|
255
|
+
await writeRuntimeDecision(workflowState.output, { output: workflowState.output, trace: { matched_rule: "workflow_boundary" } }, null);
|
|
256
|
+
return workflowState.output;
|
|
112
257
|
}
|
|
113
258
|
wfStateRaw = workflowState.state;
|
|
114
259
|
if (wfStateRaw && wfStateRaw.active) {
|
|
@@ -121,9 +266,9 @@ async function main() {
|
|
|
121
266
|
};
|
|
122
267
|
const artifactFacts = await resolveWorkflowArtifactFactsForHook(projectDir, ir, wfStateRaw, event, sessionId);
|
|
123
268
|
if ("output" in artifactFacts) {
|
|
124
|
-
writeOutput(artifactFacts.output);
|
|
125
269
|
appendArtifactResolverTrace(projectDir, event, wfStateRaw, artifactFacts.output, sessionId);
|
|
126
|
-
|
|
270
|
+
await writeRuntimeDecision(artifactFacts.output, { output: artifactFacts.output, trace: { matched_rule: "handoff", step_id: wfStateRaw.current_step } }, wfStateRaw);
|
|
271
|
+
return artifactFacts.output;
|
|
127
272
|
}
|
|
128
273
|
state.artifactFacts = artifactFacts.facts;
|
|
129
274
|
}
|
|
@@ -139,9 +284,9 @@ async function main() {
|
|
|
139
284
|
if (event === "PreToolUse") {
|
|
140
285
|
const gateResult = await handlePreToolGates(ir, rawInput, wfStateRaw, projectDir, sessionId);
|
|
141
286
|
if (gateResult) {
|
|
142
|
-
|
|
287
|
+
const traceId = randomUUID();
|
|
143
288
|
appendTrace(projectDir, {
|
|
144
|
-
trace_id:
|
|
289
|
+
trace_id: traceId,
|
|
145
290
|
event,
|
|
146
291
|
tool_name: typeof rawInput.tool_name === "string" ? rawInput.tool_name : undefined,
|
|
147
292
|
agent_type: typeof rawInput.agent_type === "string" ? rawInput.agent_type : undefined,
|
|
@@ -152,24 +297,25 @@ async function main() {
|
|
|
152
297
|
duration_ms: 0,
|
|
153
298
|
timestamp: new Date().toISOString(),
|
|
154
299
|
}, sessionId).catch(() => { });
|
|
155
|
-
|
|
300
|
+
await writeRuntimeDecision(gateResult.output, { output: gateResult.output, trace: { matched_rule: gateResult.matched_rule, step_id: wfStateRaw?.current_step } }, wfStateRaw, traceId);
|
|
301
|
+
return gateResult.output;
|
|
156
302
|
}
|
|
157
303
|
}
|
|
158
304
|
// Special handling for Stop — needs async workflow state read + session summary
|
|
159
305
|
if (event === "Stop") {
|
|
160
306
|
const workflowState = await readWorkflowStateForHook(projectDir, event, sessionId);
|
|
161
307
|
if ("output" in workflowState) {
|
|
162
|
-
|
|
163
|
-
return;
|
|
308
|
+
await writeRuntimeDecision(workflowState.output, { output: workflowState.output, trace: { matched_rule: "workflow_boundary" } }, null);
|
|
309
|
+
return workflowState.output;
|
|
164
310
|
}
|
|
165
311
|
const wfState = workflowState.state;
|
|
166
312
|
let stopArtifactFacts = [];
|
|
167
313
|
if (wfState?.active) {
|
|
168
314
|
const artifactFacts = await finalizeAndResolveArtifactsForHook(projectDir, ir, wfState, event, sessionId);
|
|
169
315
|
if ("output" in artifactFacts) {
|
|
170
|
-
writeOutput(artifactFacts.output);
|
|
171
316
|
appendArtifactResolverTrace(projectDir, event, wfState, artifactFacts.output, sessionId);
|
|
172
|
-
|
|
317
|
+
await writeRuntimeDecision(artifactFacts.output, { output: artifactFacts.output, trace: { matched_rule: "handoff", step_id: wfState.current_step } }, wfState);
|
|
318
|
+
return artifactFacts.output;
|
|
173
319
|
}
|
|
174
320
|
stopArtifactFacts = artifactFacts.facts;
|
|
175
321
|
}
|
|
@@ -217,10 +363,10 @@ async function main() {
|
|
|
217
363
|
}
|
|
218
364
|
}
|
|
219
365
|
catch { /* fail-open */ }
|
|
220
|
-
writeOutput(stopOutput);
|
|
221
366
|
// Trace for Stop
|
|
367
|
+
const traceId = randomUUID();
|
|
222
368
|
appendTrace(projectDir, {
|
|
223
|
-
trace_id:
|
|
369
|
+
trace_id: traceId,
|
|
224
370
|
event: "Stop",
|
|
225
371
|
workflow: wfState?.workflow,
|
|
226
372
|
step: wfState?.current_step,
|
|
@@ -228,11 +374,12 @@ async function main() {
|
|
|
228
374
|
duration_ms: 0,
|
|
229
375
|
timestamp: new Date().toISOString(),
|
|
230
376
|
}, sessionId).catch(() => { });
|
|
231
|
-
|
|
377
|
+
await writeRuntimeDecision(stopOutput, { output: stopOutput, trace: { matched_rule: "validator", step_id: wfState?.current_step } }, wfState, traceId);
|
|
378
|
+
return stopOutput;
|
|
232
379
|
}
|
|
233
380
|
// Dispatch to enforcement engine with timing
|
|
234
381
|
const start = Date.now();
|
|
235
|
-
let result = dispatch(event, ir, rawInput, state);
|
|
382
|
+
let result = dispatch(event, ir, rawInput, state, roles);
|
|
236
383
|
let output = result?.output ?? silentOutput();
|
|
237
384
|
const durationMs = Date.now() - start;
|
|
238
385
|
// PostToolUse side effects: session read tracking + surgeon reflection gate.
|
|
@@ -304,26 +451,28 @@ async function main() {
|
|
|
304
451
|
}
|
|
305
452
|
catch { /* fail-open: pattern detection never blocks */ }
|
|
306
453
|
}
|
|
307
|
-
writeOutput(output);
|
|
308
454
|
// Trace logging (async, fail-open)
|
|
309
|
-
const decision = output
|
|
310
|
-
|
|
311
|
-
|
|
455
|
+
const decision = normalizeDecision(output);
|
|
456
|
+
const traceDecision = decision === "block" ? "block" : decision === "allow" ? "allow" : "warn";
|
|
457
|
+
const traceId = randomUUID();
|
|
458
|
+
const decisionReason = decision !== "allow"
|
|
459
|
+
? (output.reason ?? output.hookSpecificOutput?.additionalContext ?? "unknown")
|
|
460
|
+
: "allowed by active policy";
|
|
461
|
+
const traceTimestamp = new Date().toISOString();
|
|
312
462
|
appendTrace(projectDir, {
|
|
313
|
-
trace_id:
|
|
463
|
+
trace_id: traceId,
|
|
314
464
|
event,
|
|
315
465
|
tool_name: typeof rawInput.tool_name === "string" ? rawInput.tool_name : undefined,
|
|
316
466
|
agent_type: typeof rawInput.agent_type === "string" ? rawInput.agent_type : undefined,
|
|
317
467
|
workflow: state.workflowState?.workflow,
|
|
318
468
|
step: state.workflowState?.current_step,
|
|
319
|
-
decision:
|
|
320
|
-
reason: decision !== "allow"
|
|
321
|
-
? (output.reason ?? output.hookSpecificOutput?.additionalContext)
|
|
322
|
-
: undefined,
|
|
469
|
+
decision: traceDecision,
|
|
470
|
+
reason: decision !== "allow" ? decisionReason : undefined,
|
|
323
471
|
target_path: decision !== "allow" ? targetPath : undefined,
|
|
324
472
|
duration_ms: durationMs,
|
|
325
|
-
timestamp:
|
|
473
|
+
timestamp: traceTimestamp,
|
|
326
474
|
}, sessionId).catch(() => { }); // Fail-open
|
|
475
|
+
await writeRuntimeDecision(output, result, state.workflowState, traceId);
|
|
327
476
|
// Side effect: rotate traces + clean stale state on SessionStart
|
|
328
477
|
if (event === "SessionStart") {
|
|
329
478
|
rotateTraces(projectDir).catch(() => { });
|
|
@@ -339,9 +488,10 @@ async function main() {
|
|
|
339
488
|
session_id: sessionId,
|
|
340
489
|
}).catch(() => { }); // Fail-open
|
|
341
490
|
}
|
|
491
|
+
return output;
|
|
342
492
|
}
|
|
343
493
|
// ── Dispatch ───────────────────────────────────────────────
|
|
344
|
-
function dispatch(event, ir, input, state) {
|
|
494
|
+
function dispatch(event, ir, input, state, roles) {
|
|
345
495
|
switch (event) {
|
|
346
496
|
case "PreToolUse":
|
|
347
497
|
return enforcePreToolUse(ir, {
|
|
@@ -351,7 +501,7 @@ function dispatch(event, ir, input, state) {
|
|
|
351
501
|
cwd: typeof input.cwd === "string" ? input.cwd : undefined,
|
|
352
502
|
sessionId: typeof input.session_id === "string" ? input.session_id
|
|
353
503
|
: typeof input.sessionId === "string" ? input.sessionId : undefined,
|
|
354
|
-
}, state);
|
|
504
|
+
}, state, roles);
|
|
355
505
|
case "PostToolUse":
|
|
356
506
|
return enforcePostToolUse(ir, {
|
|
357
507
|
tool_name: String(input.tool_name ?? ""),
|
|
@@ -574,7 +724,7 @@ async function finalizeAndResolveArtifacts(projectDir, ir, wfState, sessionId) {
|
|
|
574
724
|
}
|
|
575
725
|
function artifactResolverErrorOutput(event, error) {
|
|
576
726
|
const detail = error instanceof Error ? error.message : String(error);
|
|
577
|
-
return blockOutput(`[Intent DNA] ${event} artifact resolver failed: ${detail}
|
|
727
|
+
return blockOutput(`[Intent DNA] ${event} artifact resolver failed: ${detail}. Fix the workflow/session identifiers in DNA state, then rerun the hook or \`dna sync\`.`);
|
|
578
728
|
}
|
|
579
729
|
async function readWorkflowStateForHook(projectDir, event, sessionId) {
|
|
580
730
|
try {
|
|
@@ -689,7 +839,8 @@ export function appendStopVerifierWarnings(output, verifierResults, currentStep)
|
|
|
689
839
|
if (warningVerifierFailures.length === 0)
|
|
690
840
|
return output;
|
|
691
841
|
const warningText = `[Intent DNA] Verifier warnings at step '${currentStep ?? "unknown"}':\n` +
|
|
692
|
-
warningVerifierFailures.map((result) => ` - ${result.message ?? result.verifier_id}`).join("\n")
|
|
842
|
+
warningVerifierFailures.map((result) => ` - ${result.message ?? result.verifier_id}`).join("\n") +
|
|
843
|
+
"\nFix the warning above before relying on this step, or rerun the verifier after repair.";
|
|
693
844
|
return appendOutputText(output, warningText, "Stop");
|
|
694
845
|
}
|
|
695
846
|
function trimEvidence(raw) {
|
|
@@ -1521,7 +1672,20 @@ function toKebabCase(s) {
|
|
|
1521
1672
|
.toLowerCase();
|
|
1522
1673
|
}
|
|
1523
1674
|
// ── Entry Point ────────────────────────────────────────────
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1675
|
+
function isDirectEntryPoint() {
|
|
1676
|
+
const entry = process.argv[1];
|
|
1677
|
+
if (!entry)
|
|
1678
|
+
return false;
|
|
1679
|
+
try {
|
|
1680
|
+
return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));
|
|
1681
|
+
}
|
|
1682
|
+
catch {
|
|
1683
|
+
return resolve(entry) === resolve(fileURLToPath(import.meta.url));
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
if (isDirectEntryPoint()) {
|
|
1687
|
+
main().catch(() => {
|
|
1688
|
+
// Fail-open: never block Claude Code on unexpected errors
|
|
1689
|
+
writeOutput(silentOutput());
|
|
1690
|
+
});
|
|
1691
|
+
}
|