intentdna 1.7.0 → 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/feedback.d.ts +2 -2
- package/dist/cli/commands/feedback.js +5 -5
- package/dist/cli/commands/sync.js +8 -5
- package/dist/cli/commands/verify.d.ts +32 -0
- package/dist/cli/commands/verify.js +173 -6
- package/dist/cli/index.js +3 -1
- package/dist/compiler/activate.js +11 -6
- package/dist/compiler/cascade.d.ts +3 -1
- package/dist/compiler/cascade.js +23 -1
- package/dist/compiler/compile.js +1 -0
- package/dist/compiler/index.d.ts +5 -1
- package/dist/compiler/index.js +7 -4
- package/dist/compiler/input-resolver.d.ts +8 -1
- package/dist/compiler/input-resolver.js +128 -22
- 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.js +162 -13
- package/dist/hooks/state.d.ts +3 -10
- package/dist/hooks/state.js +147 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/tools-observability.js +3 -3
- 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 +84 -2
- package/dist/report/report-package.d.ts +9 -1
- package/dist/report/report-package.js +32 -27
- package/dist/runtime/claude-sdk.d.ts +9 -4
- package/dist/runtime/claude-sdk.js +9 -0
- package/dist/runtime/plugin-adapter.d.ts +5 -1
- package/dist/runtime/plugin-adapter.js +2 -0
- package/dist/schema/types.d.ts +51 -0
- package/package.json +1 -1
- package/spec/README.md +1 -1
- package/spec/schema-spec.md +78 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"name": "intentdna",
|
|
11
11
|
"description": "Declarative policy layer for AI agent behavior with plugin-managed hook runtime for Claude Code.",
|
|
12
|
-
"version": "1.7.
|
|
12
|
+
"version": "1.7.4",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "Samuel"
|
|
15
15
|
},
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
]
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
|
-
"version": "1.7.
|
|
28
|
+
"version": "1.7.4"
|
|
29
29
|
}
|
package/README.md
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
# Intent DNA
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Enterprise safety control plane for AI agent behavior.**
|
|
4
4
|
|
|
5
|
-
Intent DNA
|
|
5
|
+
Intent DNA lets an organization declare safety policy once, cascade it through Species → Enterprise → Project → Personal → Role → Context → Task, and compile it into deterministic gates for every agent, project, and harness. It governs how agents operate across existing tools and systems; it does not replace your business systems or act as a domain-specific automation runtime.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## What It Is
|
|
10
10
|
|
|
11
|
-
Intent DNA is
|
|
11
|
+
Intent DNA is an enforcement-first control plane for organization-wide agent safety:
|
|
12
12
|
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
13
|
+
- **Enterprise / Species policy bundles** define shared red lines across projects and teams
|
|
14
|
+
- **Cascade governance** preserves provenance and prevents lower layers from relaxing organization thresholds
|
|
15
|
+
- **Constraint IR** carries policy provenance into hook, SDK-check, and CI strict gates
|
|
16
|
+
- **RuntimeDecisionEvent evidence** records who/what/why/from which policy for every allow/warn/block/escalate/validate decision
|
|
17
|
+
- **MCP, reports, state, trace, audit, and evolution** query or package evidence; they are not the policy source of truth
|
|
17
18
|
|
|
18
19
|
It governs the agents operating on your systems rather than re-implementing the systems themselves.
|
|
19
20
|
|
|
20
21
|
The core model is:
|
|
21
22
|
|
|
22
23
|
```text
|
|
23
|
-
|
|
24
|
+
Enterprise / Species Policy Bundle
|
|
25
|
+
→ Species→Enterprise→Project→Personal→Role→Context→Task Cascade
|
|
26
|
+
→ Constraint IR + Provenance Manifest
|
|
27
|
+
→ Hook / SDK-check / CI strict gate
|
|
28
|
+
→ RuntimeDecisionEvent
|
|
29
|
+
→ MCP / report / dashboard / evolution evidence layer
|
|
24
30
|
```
|
|
25
31
|
|
|
26
32
|
For the architectural model and long-term design, see [DESIGN.md](DESIGN.md).
|
|
@@ -30,9 +36,10 @@ For the schema contract, see [spec/schema-spec.md](spec/schema-spec.md).
|
|
|
30
36
|
## Design Principles
|
|
31
37
|
|
|
32
38
|
- Govern agents rather than re-implementing business systems.
|
|
33
|
-
- Declare
|
|
34
|
-
-
|
|
35
|
-
-
|
|
39
|
+
- Declare enterprise safety red lines first, then compile them into native control surfaces.
|
|
40
|
+
- Treat Species and Enterprise thresholds as non-relaxable by Project, Personal, Role, Context, or Task layers.
|
|
41
|
+
- Keep enforcement points authoritative: hooks, SDK checks, and CI gates make decisions from compiled IR and provenance.
|
|
42
|
+
- Treat runtime state, validators, traces, reports, MCP queries, and signals as evidence assets, not policy truth.
|
|
36
43
|
- Keep adapters and signal ingestion pluggable so the control plane outlives any single tool.
|
|
37
44
|
|
|
38
45
|
---
|
|
@@ -70,10 +77,10 @@ dna init --upgrade <template-name>
|
|
|
70
77
|
|
|
71
78
|
## How It Works
|
|
72
79
|
|
|
73
|
-
Intent DNA compiles to your tool's native control surfaces:
|
|
80
|
+
Intent DNA compiles organization policy to your tool's native control surfaces:
|
|
74
81
|
|
|
75
82
|
```text
|
|
76
|
-
DNA → Constraint IR →
|
|
83
|
+
DNA → Cascade provenance → Constraint IR → hooks / SDK checks / CI gates → evidence
|
|
77
84
|
```
|
|
78
85
|
|
|
79
86
|
Typical outputs include:
|
|
@@ -82,12 +89,14 @@ Typical outputs include:
|
|
|
82
89
|
|--------|---------|
|
|
83
90
|
| `CLAUDE.md` | Behavioral guidance |
|
|
84
91
|
| `.dna/compiled/ir.json` | Runtime-readable compiled constraints |
|
|
92
|
+
| provenance manifest | Policy bundle, cascade layers, winners, and compiled IR hash |
|
|
93
|
+
| RuntimeDecisionEvent | Enterprise evidence for allow/warn/block/escalate/validate decisions |
|
|
85
94
|
| `.dna/state/sessions/*/handoffs/*.json` | Local artifact manifests for workflow handoff facts |
|
|
86
95
|
| `.claude/agents/*.md` | Role definitions |
|
|
87
96
|
| `.claude/skills/*/SKILL.md` | Workflow entrypoints |
|
|
88
97
|
| `.claude/settings.json` or plugin runtime | Hook wiring |
|
|
89
98
|
|
|
90
|
-
**Key idea:** prompts can drift, but
|
|
99
|
+
**Key idea:** prompts can drift, but non-relaxable enterprise thresholds, compiled gates, and provenance-backed runtime evidence make behavior governable.
|
|
91
100
|
|
|
92
101
|
---
|
|
93
102
|
|
|
@@ -95,11 +104,13 @@ Typical outputs include:
|
|
|
95
104
|
|
|
96
105
|
Intent DNA currently focuses on:
|
|
97
106
|
|
|
107
|
+
- Enterprise Safety Slice: shared policy bundles, cascade provenance, and non-relaxable organization thresholds
|
|
98
108
|
- Claude Code / Claude-style harnesses
|
|
99
109
|
- Role-based agent workflows
|
|
100
110
|
- Template-driven project bootstrap and upgrades
|
|
101
111
|
- Deterministic guardrails and stateful enforcement
|
|
102
112
|
- Resolver-backed artifact handoff between workflow steps
|
|
113
|
+
- Evidence-layer MCP/report/state/trace/audit surfaces
|
|
103
114
|
- Dogfood-driven template iteration
|
|
104
115
|
|
|
105
116
|
Adapter and ecosystem direction are tracked in [ROADMAP.md](ROADMAP.md), not duplicated here.
|
|
@@ -157,7 +168,7 @@ workflows:
|
|
|
157
168
|
description: Review the implementation
|
|
158
169
|
```
|
|
159
170
|
|
|
160
|
-
**Cascade:** DNA files inherit and override by priority. `threshold` codons (safety rules)
|
|
171
|
+
**Cascade:** DNA files inherit and override by priority. `threshold` codons (safety rules) from Species or Enterprise are red lines: Project, Personal, Role, Context, and Task layers may add stricter rules but cannot relax them.
|
|
161
172
|
|
|
162
173
|
**Genes:** Behavioral traits (attract/repel/threshold/weight/sense).
|
|
163
174
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dna feedback [--days <N>] [--json] [--evolve]
|
|
3
3
|
*
|
|
4
|
-
* Build a local kernel observability report from
|
|
5
|
-
*
|
|
4
|
+
* Build a local kernel observability report from RuntimeDecisionEvent evidence.
|
|
5
|
+
* Legacy traces and verifier results are included as diagnostic context only.
|
|
6
6
|
*/
|
|
7
7
|
export interface FeedbackOptions {
|
|
8
8
|
days: number;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dna feedback [--days <N>] [--json] [--evolve]
|
|
3
3
|
*
|
|
4
|
-
* Build a local kernel observability report from
|
|
5
|
-
*
|
|
4
|
+
* Build a local kernel observability report from RuntimeDecisionEvent evidence.
|
|
5
|
+
* Legacy traces and verifier results are included as diagnostic context only.
|
|
6
6
|
*/
|
|
7
7
|
import { buildKernelReport, formatKernelReport } from "../../report/kernel-report.js";
|
|
8
8
|
import { buildReportPackage } from "../../report/report-package.js";
|
|
@@ -23,9 +23,9 @@ export async function runFeedback(opts) {
|
|
|
23
23
|
includeMarkerPreview: opts.evolve,
|
|
24
24
|
dnaId: opts.dnaId,
|
|
25
25
|
});
|
|
26
|
-
if (report.summary.
|
|
27
|
-
process.stderr.write(`No
|
|
28
|
-
process.stderr.write("
|
|
26
|
+
if (report.summary.runtime_decisions === 0 && report.summary.legacy_diagnostics === 0) {
|
|
27
|
+
process.stderr.write(`No RuntimeDecisionEvent evidence or legacy diagnostics found for the last ${opts.days} day(s).\n`);
|
|
28
|
+
process.stderr.write("RuntimeDecisionEvent evidence is generated by dna-hook during Claude Code sessions.\n");
|
|
29
29
|
return 0;
|
|
30
30
|
}
|
|
31
31
|
if (opts.json) {
|
|
@@ -14,7 +14,7 @@ import { resolve, dirname } from "node:path";
|
|
|
14
14
|
import { readFile, writeFile as writeFileAsync, stat, mkdir, readdir, unlink, rm } from "node:fs/promises";
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
16
|
import { createInterface } from "node:readline";
|
|
17
|
-
import { assertNoNamespaceCollisions, compileFromFiles, detectDNAConfigs,
|
|
17
|
+
import { assertNoNamespaceCollisions, compileFromFiles, detectDNAConfigs, expandDNAInputs, loadDNA, } from "../../compiler/index.js";
|
|
18
18
|
import { parseYAML } from "../../schema/yaml-parser.js";
|
|
19
19
|
import { findTemplateCatalogEntry } from "../../templates/catalog.js";
|
|
20
20
|
import { compileToMarkdown, injectIntoFile, removeFromFile } from "../../runtime/markdown.js";
|
|
@@ -318,7 +318,7 @@ function lockTrackedOutputPaths(trackedOutputs) {
|
|
|
318
318
|
.filter((output) => getSyncOutputArtifactMetadata(output.artifact)?.trackedByLock)
|
|
319
319
|
.map((output) => output.path);
|
|
320
320
|
}
|
|
321
|
-
async function generateLockFile(lockPath, dnaSources, outputFiles) {
|
|
321
|
+
async function generateLockFile(lockPath, dnaSources, outputFiles, provenance, compiledIrHash) {
|
|
322
322
|
const outputs = {};
|
|
323
323
|
for (const f of outputFiles) {
|
|
324
324
|
outputs[f] = { sha256: await computeFileHash(f) };
|
|
@@ -327,6 +327,8 @@ async function generateLockFile(lockPath, dnaSources, outputFiles) {
|
|
|
327
327
|
synced_at: new Date().toISOString(),
|
|
328
328
|
dna_sources: dnaSources,
|
|
329
329
|
outputs,
|
|
330
|
+
compiled_ir_hash: compiledIrHash,
|
|
331
|
+
provenance,
|
|
330
332
|
};
|
|
331
333
|
await mkdir(dirname(lockPath), { recursive: true });
|
|
332
334
|
await writeFileAsync(lockPath, JSON.stringify(lock, null, 2) + "\n", "utf-8");
|
|
@@ -441,7 +443,8 @@ export async function runSync(opts) {
|
|
|
441
443
|
// Step 0: Expand and pre-validate all DNA files before any work
|
|
442
444
|
// loadDNA() calls validateDNA() internally — validation failures throw here,
|
|
443
445
|
// before evolve/compile/inject waste any work.
|
|
444
|
-
const
|
|
446
|
+
const expandedInputs = await expandDNAInputs(opts.files);
|
|
447
|
+
const expandedFiles = expandedInputs.files;
|
|
445
448
|
await checkTemplateVersions(expandedFiles);
|
|
446
449
|
const loadedDNAs = await Promise.all(expandedFiles.map(loadDNA));
|
|
447
450
|
if (opts.dryRun) {
|
|
@@ -464,7 +467,7 @@ export async function runSync(opts) {
|
|
|
464
467
|
}
|
|
465
468
|
}
|
|
466
469
|
// Step 2: Compile
|
|
467
|
-
const ir = await compileFromFiles(
|
|
470
|
+
const ir = await compileFromFiles(opts.files, {
|
|
468
471
|
context: opts.context,
|
|
469
472
|
role: opts.role,
|
|
470
473
|
epigenetic_markers: epigeneticMarkers,
|
|
@@ -721,7 +724,7 @@ export async function runSync(opts) {
|
|
|
721
724
|
process.stderr.write(`Dry-run: would write lock file: ${lockPath}\n`);
|
|
722
725
|
}
|
|
723
726
|
else {
|
|
724
|
-
await generateLockFile(lockPath,
|
|
727
|
+
await generateLockFile(lockPath, opts.files, lockTrackedOutputPaths(trackedOutputs), ir.provenance, ir.compiled_ir_hash);
|
|
725
728
|
process.stderr.write(`Lock file: ${lockPath}\n`);
|
|
726
729
|
}
|
|
727
730
|
trackSyncOutput(trackedOutputs, "lock-file", lockPath);
|
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
* 1 — drift detected (modified or missing files)
|
|
10
10
|
* 2 — usage error (e.g. lock file not found)
|
|
11
11
|
*/
|
|
12
|
+
import type { CompiledProvenanceManifest } from "../../schema/types.js";
|
|
12
13
|
export interface VerifyOptions {
|
|
13
14
|
lockFile: string;
|
|
14
15
|
stats?: boolean;
|
|
15
16
|
health?: boolean;
|
|
17
|
+
strict?: boolean;
|
|
18
|
+
projectDir?: string;
|
|
16
19
|
}
|
|
17
20
|
export interface LockFileEntry {
|
|
18
21
|
sha256: string;
|
|
@@ -21,6 +24,8 @@ export interface LockFile {
|
|
|
21
24
|
synced_at: string;
|
|
22
25
|
dna_sources: string[];
|
|
23
26
|
outputs: Record<string, LockFileEntry>;
|
|
27
|
+
compiled_ir_hash?: string;
|
|
28
|
+
provenance?: CompiledProvenanceManifest;
|
|
24
29
|
}
|
|
25
30
|
export interface VerifyResult {
|
|
26
31
|
file: string;
|
|
@@ -28,9 +33,36 @@ export interface VerifyResult {
|
|
|
28
33
|
expected?: string;
|
|
29
34
|
actual?: string;
|
|
30
35
|
}
|
|
36
|
+
export interface ProvenanceVerifyResult {
|
|
37
|
+
source_id: string;
|
|
38
|
+
file: string;
|
|
39
|
+
status: "match" | "mismatch" | "missing";
|
|
40
|
+
expected?: string;
|
|
41
|
+
actual?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface InheritanceGraphVerifyResult {
|
|
44
|
+
source_id: string;
|
|
45
|
+
file: string;
|
|
46
|
+
status: "match" | "mismatch" | "missing" | "extra";
|
|
47
|
+
expected?: string;
|
|
48
|
+
actual?: string;
|
|
49
|
+
}
|
|
31
50
|
export declare function sha256(content: string): string;
|
|
32
51
|
export declare function parseLockFile(raw: string): LockFile;
|
|
33
52
|
export declare function verifyLock(lock: LockFile): Promise<VerifyResult[]>;
|
|
53
|
+
export declare function verifyInheritanceGraph(lock: LockFile): Promise<InheritanceGraphVerifyResult[]>;
|
|
54
|
+
export declare function verifyProvenance(provenance?: CompiledProvenanceManifest): Promise<ProvenanceVerifyResult[]>;
|
|
55
|
+
export interface StrictRuntimeEvidenceExpectation {
|
|
56
|
+
compiledIrHash?: string;
|
|
57
|
+
policyBundleId?: string;
|
|
58
|
+
policyBundleVersion?: string;
|
|
59
|
+
}
|
|
60
|
+
export declare function verifyStrictRuntimeEvidence(projectDir: string, expected?: StrictRuntimeEvidenceExpectation): Promise<{
|
|
61
|
+
ok: boolean;
|
|
62
|
+
total: number;
|
|
63
|
+
enterprise: number;
|
|
64
|
+
errors: string[];
|
|
65
|
+
}>;
|
|
34
66
|
export declare function runVerify(opts: VerifyOptions): Promise<number>;
|
|
35
67
|
export interface HealthCheckResult {
|
|
36
68
|
name: string;
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { readFile } from "node:fs/promises";
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
|
-
import {
|
|
14
|
+
import { dirname, resolve } from "node:path";
|
|
15
|
+
import { readRuntimeDecisionEvents, readTraces } from "../../hooks/state.js";
|
|
16
|
+
import { expandDNAInputs, formatDiagnostics } from "../../compiler/index.js";
|
|
17
|
+
import { DNADiagnosticsError } from "../../compiler/diagnostics.js";
|
|
18
|
+
import { validateRuntimeDecisionEvent } from "../../governance/index.js";
|
|
15
19
|
// ── Helpers ────────────────────────────────────────────────
|
|
16
20
|
export function sha256(content) {
|
|
17
21
|
return createHash("sha256").update(content, "utf-8").digest("hex");
|
|
@@ -52,6 +56,108 @@ export async function verifyLock(lock) {
|
|
|
52
56
|
}
|
|
53
57
|
return results;
|
|
54
58
|
}
|
|
59
|
+
function enterpriseDirsFromLock(lock) {
|
|
60
|
+
const dirs = new Set();
|
|
61
|
+
for (const source of lock.provenance?.sources ?? []) {
|
|
62
|
+
if (source.source_ref.startsWith("enterprise:"))
|
|
63
|
+
dirs.add(dirname(source.resolved_path));
|
|
64
|
+
}
|
|
65
|
+
return [...dirs];
|
|
66
|
+
}
|
|
67
|
+
function sourceGraphKey(source) {
|
|
68
|
+
return JSON.stringify([source.source_id, source.source_ref, source.inherited_by ?? ""]);
|
|
69
|
+
}
|
|
70
|
+
function comparableSource(source) {
|
|
71
|
+
return JSON.stringify({
|
|
72
|
+
source_ref: source.source_ref,
|
|
73
|
+
resolved_path: source.resolved_path,
|
|
74
|
+
fingerprint: source.fingerprint,
|
|
75
|
+
inherited_by: source.inherited_by,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export async function verifyInheritanceGraph(lock) {
|
|
79
|
+
if (!lock.provenance)
|
|
80
|
+
return [];
|
|
81
|
+
const expanded = await expandDNAInputs(lock.dna_sources, { enterprisePolicyDirs: enterpriseDirsFromLock(lock) });
|
|
82
|
+
const current = new Map(expanded.provenance.map((source) => [sourceGraphKey(source), source]));
|
|
83
|
+
const locked = new Map(lock.provenance.sources.map((source) => [sourceGraphKey(source), source]));
|
|
84
|
+
const results = [];
|
|
85
|
+
for (const [key, expected] of locked) {
|
|
86
|
+
const actual = current.get(key);
|
|
87
|
+
if (!actual) {
|
|
88
|
+
results.push({ source_id: expected.source_id, file: expected.resolved_path, status: "missing", expected: comparableSource(expected) });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const expectedComparable = comparableSource(expected);
|
|
92
|
+
const actualComparable = comparableSource(actual);
|
|
93
|
+
results.push({
|
|
94
|
+
source_id: expected.source_id,
|
|
95
|
+
file: expected.resolved_path,
|
|
96
|
+
status: expectedComparable === actualComparable ? "match" : "mismatch",
|
|
97
|
+
expected: expectedComparable,
|
|
98
|
+
actual: actualComparable,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
for (const [key, actual] of current) {
|
|
102
|
+
if (!locked.has(key)) {
|
|
103
|
+
results.push({ source_id: actual.source_id, file: actual.resolved_path, status: "extra", actual: comparableSource(actual) });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return results;
|
|
107
|
+
}
|
|
108
|
+
export async function verifyProvenance(provenance) {
|
|
109
|
+
if (!provenance)
|
|
110
|
+
return [];
|
|
111
|
+
const results = [];
|
|
112
|
+
for (const source of provenance.sources) {
|
|
113
|
+
try {
|
|
114
|
+
const content = await readFile(source.resolved_path, "utf-8");
|
|
115
|
+
const actual = `sha256:${sha256(content)}`;
|
|
116
|
+
results.push({
|
|
117
|
+
source_id: source.source_id,
|
|
118
|
+
file: source.resolved_path,
|
|
119
|
+
status: actual === source.fingerprint ? "match" : "mismatch",
|
|
120
|
+
expected: source.fingerprint,
|
|
121
|
+
actual,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
results.push({
|
|
126
|
+
source_id: source.source_id,
|
|
127
|
+
file: source.resolved_path,
|
|
128
|
+
status: "missing",
|
|
129
|
+
expected: source.fingerprint,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
export async function verifyStrictRuntimeEvidence(projectDir, expected = {}) {
|
|
136
|
+
const events = await readRuntimeDecisionEvents(projectDir, 7);
|
|
137
|
+
const errors = [];
|
|
138
|
+
let enterprise = 0;
|
|
139
|
+
for (const event of events) {
|
|
140
|
+
const result = validateRuntimeDecisionEvent(event);
|
|
141
|
+
if (!result.valid)
|
|
142
|
+
errors.push(...result.errors.map((error) => `${event.event_id ?? "unknown"}: ${error}`));
|
|
143
|
+
if (expected.compiledIrHash && event.compiled_ir_hash !== expected.compiledIrHash) {
|
|
144
|
+
errors.push(`${event.event_id ?? "unknown"}: compiled_ir_hash does not match current lock`);
|
|
145
|
+
}
|
|
146
|
+
if (expected.policyBundleId && event.policy_bundle_id !== expected.policyBundleId) {
|
|
147
|
+
errors.push(`${event.event_id ?? "unknown"}: policy_bundle_id does not match current lock`);
|
|
148
|
+
}
|
|
149
|
+
if (expected.policyBundleVersion && event.policy_bundle_version !== expected.policyBundleVersion) {
|
|
150
|
+
errors.push(`${event.event_id ?? "unknown"}: policy_bundle_version does not match current lock`);
|
|
151
|
+
}
|
|
152
|
+
if (result.classification === "enterprise_evidence")
|
|
153
|
+
enterprise++;
|
|
154
|
+
else
|
|
155
|
+
errors.push(`${event.event_id ?? "unknown"}: RuntimeDecisionEvent is diagnostic-only, not enterprise evidence`);
|
|
156
|
+
}
|
|
157
|
+
if (events.length === 0)
|
|
158
|
+
errors.push("no RuntimeDecisionEvent evidence found");
|
|
159
|
+
return { ok: errors.length === 0, total: events.length, enterprise, errors };
|
|
160
|
+
}
|
|
55
161
|
// ── CLI entry ──────────────────────────────────────────────
|
|
56
162
|
export async function runVerify(opts) {
|
|
57
163
|
// Handle --health mode
|
|
@@ -79,8 +185,24 @@ export async function runVerify(opts) {
|
|
|
79
185
|
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
80
186
|
return 2;
|
|
81
187
|
}
|
|
188
|
+
let inheritanceResults = [];
|
|
189
|
+
if (lock.provenance) {
|
|
190
|
+
try {
|
|
191
|
+
inheritanceResults = await verifyInheritanceGraph(lock);
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
if (err instanceof DNADiagnosticsError) {
|
|
195
|
+
process.stderr.write(`Error: DNA inheritance graph is invalid:\n${formatDiagnostics(err.diagnostics)}\n`);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
process.stderr.write(`Error: DNA inheritance graph is invalid: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
199
|
+
}
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
82
203
|
// Verify
|
|
83
204
|
const results = await verifyLock(lock);
|
|
205
|
+
const provenanceResults = await verifyProvenance(lock.provenance);
|
|
84
206
|
// Report
|
|
85
207
|
let hasFailure = false;
|
|
86
208
|
for (const r of results) {
|
|
@@ -96,17 +218,62 @@ export async function runVerify(opts) {
|
|
|
96
218
|
hasFailure = true;
|
|
97
219
|
}
|
|
98
220
|
}
|
|
99
|
-
const
|
|
100
|
-
|
|
221
|
+
for (const r of inheritanceResults) {
|
|
222
|
+
if (r.status === "match") {
|
|
223
|
+
process.stderr.write(` ok inheritance ${r.source_id}: ${r.file}\n`);
|
|
224
|
+
}
|
|
225
|
+
else if (r.status === "mismatch") {
|
|
226
|
+
process.stderr.write(` MISMATCH inheritance ${r.source_id}: ${r.file}\n`);
|
|
227
|
+
hasFailure = true;
|
|
228
|
+
}
|
|
229
|
+
else if (r.status === "missing") {
|
|
230
|
+
process.stderr.write(` MISSING inheritance ${r.source_id}: ${r.file}\n`);
|
|
231
|
+
hasFailure = true;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
process.stderr.write(` EXTRA inheritance ${r.source_id}: ${r.file}\n`);
|
|
235
|
+
hasFailure = true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const r of provenanceResults) {
|
|
239
|
+
if (r.status === "match") {
|
|
240
|
+
process.stderr.write(` ok provenance ${r.source_id}: ${r.file}\n`);
|
|
241
|
+
}
|
|
242
|
+
else if (r.status === "mismatch") {
|
|
243
|
+
process.stderr.write(` MISMATCH provenance ${r.source_id}: ${r.file}\n`);
|
|
244
|
+
hasFailure = true;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
process.stderr.write(` MISSING provenance ${r.source_id}: ${r.file}\n`);
|
|
248
|
+
hasFailure = true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (lock.provenance?.compiled_ir_hash && lock.compiled_ir_hash && lock.provenance.compiled_ir_hash !== lock.compiled_ir_hash) {
|
|
252
|
+
process.stderr.write(" MISMATCH compiled_ir_hash differs between lock and provenance\n");
|
|
253
|
+
hasFailure = true;
|
|
254
|
+
}
|
|
255
|
+
const total = results.length + inheritanceResults.length + provenanceResults.length;
|
|
256
|
+
const ok = results.filter((r) => r.status === "match").length + inheritanceResults.filter((r) => r.status === "match").length + provenanceResults.filter((r) => r.status === "match").length;
|
|
101
257
|
const failed = total - ok;
|
|
102
258
|
if (hasFailure) {
|
|
103
259
|
process.stderr.write(`\nDrift detected: ${failed}/${total} file(s) changed since last sync\n`);
|
|
104
260
|
return 1;
|
|
105
261
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
262
|
+
if (opts.strict) {
|
|
263
|
+
const strictProjectDir = opts.projectDir ?? dirname(dirname(resolve(opts.lockFile)));
|
|
264
|
+
const strict = await verifyStrictRuntimeEvidence(strictProjectDir, {
|
|
265
|
+
compiledIrHash: lock.compiled_ir_hash,
|
|
266
|
+
policyBundleId: lock.provenance?.policy_bundle_id,
|
|
267
|
+
policyBundleVersion: lock.provenance?.policy_bundle_version,
|
|
268
|
+
});
|
|
269
|
+
if (!strict.ok) {
|
|
270
|
+
process.stderr.write(`\nStrict runtime evidence failed: ${strict.errors.join("; ")}\n`);
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
process.stderr.write(`\nStrict runtime evidence: ${strict.enterprise}/${strict.total} enterprise event(s) valid\n`);
|
|
109
274
|
}
|
|
275
|
+
process.stderr.write(`\nAll ${total} file(s) verified — no drift\n`);
|
|
276
|
+
return 0;
|
|
110
277
|
}
|
|
111
278
|
// ── Stats Mode ────────────────────────────────────────────
|
|
112
279
|
async function runStats() {
|
package/dist/cli/index.js
CHANGED
|
@@ -71,7 +71,7 @@ Examples:
|
|
|
71
71
|
`;
|
|
72
72
|
const COMMAND_HELP = {
|
|
73
73
|
sync: `Usage: dna sync [files...] [options]\n\nOptions:\n --mode auto|plugin|bin Sync mode (default: auto)\n --dry-run Print planned outputs without writing files\n --post-summary Print registry-backed output summary\n --target <target> Markdown target for injection\n --inject <path> File to inject markdown into\n --agents <dir> Generate agent files\n --skills <dir> Generate skill files\n --workflow <dir> Generate workflow shell scripts\n --settings <path> Write hook settings in bin mode\n --lock <path> Write lock file (default .dna/lock)\n`,
|
|
74
|
-
verify: `Usage: dna verify [--lock <path>] [--stats] [--health]\n\nOptions:\n --health Comprehensive installation health check\n --stats Show hook call statistics\n --lock <path> Lock file path (default .dna/lock)\n`,
|
|
74
|
+
verify: `Usage: dna verify [--lock <path>] [--stats] [--health] [--strict]\n\nOptions:\n --health Comprehensive installation health check\n --stats Show hook call statistics\n --strict Require valid RuntimeDecisionEvent enterprise evidence\n --lock <path> Lock file path (default .dna/lock)\n`,
|
|
75
75
|
templates: `Usage: dna templates <list|show|deploy>\n\nSubcommands:\n list List built-in and project templates\n show <name> Show template metadata and YAML\n deploy Copy local templates to global install\n`,
|
|
76
76
|
context: `Usage: dna context <index|search|doc> [query|id] [files...]\n\nSubcommands:\n index List explicitly declared planning-only advisory context sources\n search <query> Search source metadata\n doc <id> Read a local file/doc source body\n`,
|
|
77
77
|
run: `Usage: dna run --dna <file> --workflow <name> --task <id> [options]\n\nOptions:\n --dry-run Print execution plan only\n --permission-mode <mode> Claude permission mode (default: default)\n`,
|
|
@@ -166,6 +166,7 @@ async function main() {
|
|
|
166
166
|
lock: { type: "string", default: ".dna/lock" },
|
|
167
167
|
stats: { type: "boolean", default: false },
|
|
168
168
|
health: { type: "boolean", default: false },
|
|
169
|
+
strict: { type: "boolean", default: false },
|
|
169
170
|
},
|
|
170
171
|
allowPositionals: true,
|
|
171
172
|
strict: false,
|
|
@@ -175,6 +176,7 @@ async function main() {
|
|
|
175
176
|
lockFile: verifyValues.lock,
|
|
176
177
|
stats: verifyValues.stats,
|
|
177
178
|
health: verifyValues.health,
|
|
179
|
+
strict: verifyValues.strict,
|
|
178
180
|
});
|
|
179
181
|
process.exit(code);
|
|
180
182
|
break;
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
/**
|
|
11
11
|
* Apply a modifier to a gene's expression level.
|
|
12
12
|
*/
|
|
13
|
-
function applyModifier(level, mod) {
|
|
13
|
+
function applyModifier(level, mod, protectedThreshold = false) {
|
|
14
|
+
if (protectedThreshold && (mod.action === "deactivate" || mod.action === "suppress"))
|
|
15
|
+
return Math.max(level, 1.0);
|
|
14
16
|
switch (mod.action) {
|
|
15
17
|
case "amplify":
|
|
16
18
|
return level * (mod.factor ?? 2.0);
|
|
@@ -28,7 +30,7 @@ function applyModifier(level, mod) {
|
|
|
28
30
|
* Apply epigenetic markers to gene expression levels.
|
|
29
31
|
* Markers decay over time based on their decay factor and age.
|
|
30
32
|
*/
|
|
31
|
-
function applyEpigeneticMarkers(genes, markers) {
|
|
33
|
+
function applyEpigeneticMarkers(genes, markers, protectedThresholdGenes) {
|
|
32
34
|
const now = Date.now();
|
|
33
35
|
for (const marker of markers) {
|
|
34
36
|
// Calculate decay based on time elapsed
|
|
@@ -40,6 +42,8 @@ function applyEpigeneticMarkers(genes, markers) {
|
|
|
40
42
|
continue; // marker too old, skip
|
|
41
43
|
for (const effect of marker.effects) {
|
|
42
44
|
if (effect.gene && genes[effect.gene] && effect.action && effect.factor) {
|
|
45
|
+
if (protectedThresholdGenes.has(effect.gene) && effect.action === "suppress")
|
|
46
|
+
continue;
|
|
43
47
|
const gene = genes[effect.gene];
|
|
44
48
|
const effectiveFactor = 1 + (effect.factor - 1) * decayedStrength;
|
|
45
49
|
if (effect.action === "amplify") {
|
|
@@ -77,18 +81,19 @@ export function activateDNA(cascaded, contextName = null, activeRole = null) {
|
|
|
77
81
|
role: gene.role,
|
|
78
82
|
};
|
|
79
83
|
}
|
|
84
|
+
const protectedThresholdGenes = new Set(cascaded.protected_threshold_genes ?? []);
|
|
80
85
|
// Apply context modifiers if a context is active
|
|
81
86
|
if (contextName && cascaded.contexts[contextName]) {
|
|
82
87
|
const ctx = cascaded.contexts[contextName];
|
|
83
88
|
// Apply gene modifiers
|
|
84
89
|
for (const mod of ctx.modifiers ?? []) {
|
|
85
90
|
if (activated[mod.gene]) {
|
|
86
|
-
activated[mod.gene].expression_level = applyModifier(activated[mod.gene].expression_level, mod);
|
|
91
|
+
activated[mod.gene].expression_level = applyModifier(activated[mod.gene].expression_level, mod, protectedThresholdGenes.has(mod.gene));
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
// Deactivate genes
|
|
90
95
|
for (const g of ctx.deactivate_genes ?? []) {
|
|
91
|
-
if (activated[g]) {
|
|
96
|
+
if (activated[g] && !protectedThresholdGenes.has(g)) {
|
|
92
97
|
activated[g].expression_level = 0;
|
|
93
98
|
}
|
|
94
99
|
}
|
|
@@ -125,13 +130,13 @@ export function activateDNA(cascaded, contextName = null, activeRole = null) {
|
|
|
125
130
|
}
|
|
126
131
|
// Suppress genes
|
|
127
132
|
for (const geneName of role.suppresses_genes ?? []) {
|
|
128
|
-
if (activated[geneName]) {
|
|
133
|
+
if (activated[geneName] && !protectedThresholdGenes.has(geneName)) {
|
|
129
134
|
activated[geneName].expression_level *= 0.1;
|
|
130
135
|
}
|
|
131
136
|
}
|
|
132
137
|
}
|
|
133
138
|
// Apply epigenetic markers
|
|
134
|
-
applyEpigeneticMarkers(activated, cascaded.epigenetic_markers);
|
|
139
|
+
applyEpigeneticMarkers(activated, cascaded.epigenetic_markers, protectedThresholdGenes);
|
|
135
140
|
return {
|
|
136
141
|
genes: activated,
|
|
137
142
|
active_context: contextName,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - attract/repel: same target accumulates, different targets independent
|
|
11
11
|
* - amplify/suppress: factors multiply (1.5x × 2.0x = 3.0x)
|
|
12
12
|
*/
|
|
13
|
-
import type { IntentDNA, Gene, ContextRegion, EpigeneticMarker, RoleDef, WorkflowDef, LegibilityAssetMap, VerifierCommandPolicy, ControllerDef, AdvisoryContextSource, PlanningContext } from "../schema/types.js";
|
|
13
|
+
import type { IntentDNA, Gene, ContextRegion, EpigeneticMarker, RoleDef, WorkflowDef, LegibilityAssetMap, VerifierCommandPolicy, ControllerDef, AdvisoryContextSource, PlanningContext, CompiledProvenanceManifest } from "../schema/types.js";
|
|
14
14
|
export interface CascadedDNA {
|
|
15
15
|
genes: Record<string, Gene>;
|
|
16
16
|
contexts: Record<string, ContextRegion>;
|
|
@@ -30,6 +30,8 @@ export interface CascadedDNA {
|
|
|
30
30
|
context_sources?: AdvisoryContextSource[];
|
|
31
31
|
planning_context?: PlanningContext;
|
|
32
32
|
verifier_policy?: VerifierCommandPolicy;
|
|
33
|
+
provenance?: CompiledProvenanceManifest;
|
|
34
|
+
protected_threshold_genes?: string[];
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
37
|
* Cascade multiple DNA layers into a single merged DNA.
|