agent-contracts 0.11.7 → 0.12.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/README.md +39 -1
- package/dist/cli.js +171 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +42 -9
- package/dist/index.js +159 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -229,6 +229,31 @@ Workflow steps support additional properties:
|
|
|
229
229
|
* `retry` (delegate steps) — defines a conditional retry loop with `condition`, `fix_task`, and optional `revalidate_task`
|
|
230
230
|
* `routing_key` (decision steps) — the field that determines branch selection. The legacy field `on` is still accepted but deprecated due to YAML 1.1 reserved word collision
|
|
231
231
|
|
|
232
|
+
### Validation
|
|
233
|
+
|
|
234
|
+
A **Validation** defines a verification step for an artifact:
|
|
235
|
+
|
|
236
|
+
* `target_artifact` — the artifact being verified
|
|
237
|
+
* `kind` — the type of verification (see below)
|
|
238
|
+
* `executor_type` — `tool` (automated) or `agent` (agent-driven)
|
|
239
|
+
* `executor` — the tool or agent that runs the validation
|
|
240
|
+
* `blocking` — whether the validation must pass before proceeding
|
|
241
|
+
* `produces_evidence` — optional artifact produced as evidence
|
|
242
|
+
|
|
243
|
+
#### Validation kinds
|
|
244
|
+
|
|
245
|
+
| Kind | Purpose | Example |
|
|
246
|
+
|------|---------|---------|
|
|
247
|
+
| `schema` | Structural schema check | JSON Schema validation, OpenAPI lint, SQL syntax |
|
|
248
|
+
| `mechanical` | Automated tool check | CLI linters, diff checks, coverage reports |
|
|
249
|
+
| `semantic` | Meaning-level review | Agent-based review of spec intent, plan coherence |
|
|
250
|
+
| `approval` | Human/agent sign-off gate | Architect approval before implementation |
|
|
251
|
+
| `provenance` | Source derivation verification | Confirm generated artifact derives from its canonical source (e.g., manifest from API contracts) |
|
|
252
|
+
| `traceability` | Cross-artifact link completeness | Verify every spec requirement reaches contracts, tests, and code |
|
|
253
|
+
| `fidelity` | Semantic faithfulness to source | Confirm tests actually verify spec intent, not just structural compliance |
|
|
254
|
+
|
|
255
|
+
`schema` and `mechanical` are best suited for automated checks via tools. `semantic`, `fidelity`, and `approval` are typically agent-driven. `provenance` and `traceability` can be either tool or agent-based depending on the verification complexity.
|
|
256
|
+
|
|
232
257
|
### Guardrail
|
|
233
258
|
|
|
234
259
|
A **Guardrail** declares a cross-cutting constraint:
|
|
@@ -973,7 +998,7 @@ Each `context` type provides a different rendering scope:
|
|
|
973
998
|
|
|
974
999
|
| Context | Scope | Output | Key variables |
|
|
975
1000
|
|---------|-------|--------|---------------|
|
|
976
|
-
| `system` | Single file | `output` as-is | `system`, `dsl` |
|
|
1001
|
+
| `system` | Single file | `output` as-is | `system`, `dsl`, `guardrailEnforcement`\*, `bindings`\* |
|
|
977
1002
|
| `agent` | Per agent | `{agent.id}` in output path | `agent`, `receivableTasks`, `delegatableTasks`, `relatedArtifacts`, `relatedTools`, `relatedHandoffTypes`, `mergedBehavior`, `dsl` |
|
|
978
1003
|
| `task` | Per task | `{task.id}` in output path | `task`, `targetAgent`, `dsl` |
|
|
979
1004
|
| `artifact` | Per artifact | `{artifact.id}` in output path | `artifact`, `relatedTools`, `relatedValidations`, `producerAgents`, `consumerAgents`, `editorAgents`, `createdInWorkflows`, `dsl` |
|
|
@@ -1002,6 +1027,18 @@ Each `context` type provides a different rendering scope:
|
|
|
1002
1027
|
* `producerAgents` / `consumerAgents` / `editorAgents` — resolved agent records
|
|
1003
1028
|
* `createdInWorkflows` — workflow phases where this artifact is written
|
|
1004
1029
|
|
|
1030
|
+
**`system` context** includes binding-aware guardrail enforcement data when `bindings` and `active_guardrail_policy` are configured:
|
|
1031
|
+
|
|
1032
|
+
* `guardrailEnforcement` — array of enforcement entries, each with `guardrail_id`, `description`, `severity`, `action`, scoped entities (`scoped_agents`, `scoped_tasks`, `scoped_workflows`, `scoped_tools`, `scoped_artifacts`), `allow_override`, `override_requires`, `trigger` (from binding matcher type), and `escalation`
|
|
1033
|
+
* `bindings` — array of loaded `SoftwareBinding` objects
|
|
1034
|
+
|
|
1035
|
+
These fields are only populated when the config specifies `bindings` and `active_guardrail_policy`. Existing templates that do not reference these fields are unaffected.
|
|
1036
|
+
|
|
1037
|
+
**Matrix helpers** are available in `context: system` templates:
|
|
1038
|
+
|
|
1039
|
+
* `guardrailCoverageMatrix` — generates a Guardrail Coverage Matrix table (guardrail × severity × action × scoped entities × trigger × override × escalation)
|
|
1040
|
+
* `taskGuardrailMatrix` — generates a Task × Guardrail cross-reference table showing which action applies to each task
|
|
1041
|
+
|
|
1005
1042
|
**`tool` context** resolves agent and artifact references:
|
|
1006
1043
|
|
|
1007
1044
|
* `invokableAgents` — agents listed in `invokable_by`
|
|
@@ -1268,6 +1305,7 @@ Checks:
|
|
|
1268
1305
|
* prerequisite readability
|
|
1269
1306
|
* artifact ownership — `produces_artifact`/`reads_artifact` in execution steps vs. artifact producers/editors/consumers
|
|
1270
1307
|
* tool commands — `commands[].reads`/`commands[].writes` reference valid artifacts and align with `output_artifacts`
|
|
1308
|
+
* semantic validation phase coverage — warns when `semantic` or `fidelity` validations only appear in late workflow phases (e.g., audit) but not earlier phases (e.g., specify, plan)
|
|
1271
1309
|
* YAML safety — warns when YAML 1.1 reserved words (`on`, `yes`, `no`, `true`, `false`, etc.) are used in positions where they may be misinterpreted by non-1.2 parsers
|
|
1272
1310
|
* naming/style issues through Spectral rules
|
|
1273
1311
|
|
package/dist/cli.js
CHANGED
|
@@ -50676,7 +50676,7 @@ var ToolSchema = z9.object({
|
|
|
50676
50676
|
import { z as z10 } from "zod";
|
|
50677
50677
|
var ValidationSchema = z10.object({
|
|
50678
50678
|
target_artifact: z10.string(),
|
|
50679
|
-
kind: z10.enum(["schema", "mechanical", "semantic", "approval"]),
|
|
50679
|
+
kind: z10.enum(["schema", "mechanical", "semantic", "approval", "provenance", "traceability", "fidelity"]),
|
|
50680
50680
|
executor_type: z10.enum(["tool", "agent"]),
|
|
50681
50681
|
executor: z10.string(),
|
|
50682
50682
|
blocking: z10.boolean(),
|
|
@@ -52800,6 +52800,68 @@ var taskOutputValidationCompletenessRule = {
|
|
|
52800
52800
|
}
|
|
52801
52801
|
};
|
|
52802
52802
|
|
|
52803
|
+
// src/linter/rules/semantic-validation-phase-coverage.ts
|
|
52804
|
+
var SEMANTIC_KINDS = /* @__PURE__ */ new Set(["semantic", "fidelity"]);
|
|
52805
|
+
var semanticValidationPhaseCoverageRule = {
|
|
52806
|
+
id: "semantic-validation-phase-coverage",
|
|
52807
|
+
description: "Semantic and fidelity validations should appear in early workflow phases, not only in late phases.",
|
|
52808
|
+
run(dsl) {
|
|
52809
|
+
const diagnostics = [];
|
|
52810
|
+
const semanticValIds = /* @__PURE__ */ new Set();
|
|
52811
|
+
for (const [valId, val] of Object.entries(dsl.validations)) {
|
|
52812
|
+
if (SEMANTIC_KINDS.has(val.kind)) {
|
|
52813
|
+
semanticValIds.add(valId);
|
|
52814
|
+
}
|
|
52815
|
+
}
|
|
52816
|
+
if (semanticValIds.size === 0) {
|
|
52817
|
+
return diagnostics;
|
|
52818
|
+
}
|
|
52819
|
+
const phaseOrder = dsl.system.default_workflow_order;
|
|
52820
|
+
if (phaseOrder.length < 2) {
|
|
52821
|
+
return diagnostics;
|
|
52822
|
+
}
|
|
52823
|
+
const phaseValidations = /* @__PURE__ */ new Map();
|
|
52824
|
+
for (const phase of phaseOrder) {
|
|
52825
|
+
phaseValidations.set(phase, /* @__PURE__ */ new Set());
|
|
52826
|
+
}
|
|
52827
|
+
for (const [phase, wf] of Object.entries(dsl.workflow)) {
|
|
52828
|
+
if (!phaseValidations.has(phase)) continue;
|
|
52829
|
+
for (const step of wf.steps) {
|
|
52830
|
+
if (step.type === "validation" && semanticValIds.has(step.validation)) {
|
|
52831
|
+
phaseValidations.get(phase).add(step.validation);
|
|
52832
|
+
}
|
|
52833
|
+
}
|
|
52834
|
+
}
|
|
52835
|
+
for (const task of Object.values(dsl.tasks)) {
|
|
52836
|
+
const phase = task.workflow;
|
|
52837
|
+
if (!phaseValidations.has(phase)) continue;
|
|
52838
|
+
for (const valId of task.validations) {
|
|
52839
|
+
if (semanticValIds.has(valId)) {
|
|
52840
|
+
phaseValidations.get(phase).add(valId);
|
|
52841
|
+
}
|
|
52842
|
+
}
|
|
52843
|
+
}
|
|
52844
|
+
const earlyBoundary = Math.ceil(phaseOrder.length / 2);
|
|
52845
|
+
const earlyPhases = phaseOrder.slice(0, earlyBoundary);
|
|
52846
|
+
const latePhases = phaseOrder.slice(earlyBoundary);
|
|
52847
|
+
const earlyHasAny = earlyPhases.some(
|
|
52848
|
+
(p2) => (phaseValidations.get(p2)?.size ?? 0) > 0
|
|
52849
|
+
);
|
|
52850
|
+
const lateWithSemantic = latePhases.filter(
|
|
52851
|
+
(p2) => (phaseValidations.get(p2)?.size ?? 0) > 0
|
|
52852
|
+
);
|
|
52853
|
+
if (!earlyHasAny && lateWithSemantic.length > 0) {
|
|
52854
|
+
diagnostics.push({
|
|
52855
|
+
ruleId: "semantic-validation-phase-coverage",
|
|
52856
|
+
severity: "warning",
|
|
52857
|
+
path: "validations",
|
|
52858
|
+
message: `Semantic validations are only referenced in workflow phases [${lateWithSemantic.join(", ")}]. Consider adding semantic review to earlier phases [${earlyPhases.join(", ")}] to catch issues before implementation.`
|
|
52859
|
+
});
|
|
52860
|
+
}
|
|
52861
|
+
return diagnostics;
|
|
52862
|
+
}
|
|
52863
|
+
};
|
|
52864
|
+
|
|
52803
52865
|
// src/linter/linter.ts
|
|
52804
52866
|
var builtinRules = [
|
|
52805
52867
|
validationCoverageRule,
|
|
@@ -52811,7 +52873,8 @@ var builtinRules = [
|
|
|
52811
52873
|
guardrailPolicyCoverageRule,
|
|
52812
52874
|
yamlReservedKeySafetyRule,
|
|
52813
52875
|
artifactRequiredValidationWiringRule,
|
|
52814
|
-
taskOutputValidationCompletenessRule
|
|
52876
|
+
taskOutputValidationCompletenessRule,
|
|
52877
|
+
semanticValidationPhaseCoverageRule
|
|
52815
52878
|
];
|
|
52816
52879
|
function lint(dsl, rules = builtinRules) {
|
|
52817
52880
|
const diagnostics = [];
|
|
@@ -53511,8 +53574,52 @@ import { dirname as dirname5 } from "path";
|
|
|
53511
53574
|
import Handlebars from "handlebars";
|
|
53512
53575
|
|
|
53513
53576
|
// src/renderer/context.ts
|
|
53514
|
-
function buildSystemContext(dsl) {
|
|
53515
|
-
|
|
53577
|
+
function buildSystemContext(dsl, options) {
|
|
53578
|
+
const ctx = { system: dsl.system, dsl };
|
|
53579
|
+
if (options?.loadedBindings && options.loadedBindings.length > 0) {
|
|
53580
|
+
ctx.bindings = options.loadedBindings.map((lb) => lb.binding);
|
|
53581
|
+
const policyName = options.activeGuardrailPolicy;
|
|
53582
|
+
const policy = policyName ? dsl.guardrail_policies[policyName] : void 0;
|
|
53583
|
+
if (policy) {
|
|
53584
|
+
ctx.guardrailEnforcement = buildGuardrailEnforcement(dsl, policy, options.loadedBindings);
|
|
53585
|
+
}
|
|
53586
|
+
}
|
|
53587
|
+
return ctx;
|
|
53588
|
+
}
|
|
53589
|
+
function buildGuardrailEnforcement(dsl, policy, loadedBindings) {
|
|
53590
|
+
const entries = [];
|
|
53591
|
+
const bindingTriggers = /* @__PURE__ */ new Map();
|
|
53592
|
+
for (const lb of loadedBindings) {
|
|
53593
|
+
const impl = lb.binding.guardrail_impl ?? {};
|
|
53594
|
+
for (const [guardrailId, gi] of Object.entries(impl)) {
|
|
53595
|
+
for (const check of gi.checks) {
|
|
53596
|
+
if (check.matcher) {
|
|
53597
|
+
bindingTriggers.set(guardrailId, check.matcher.type);
|
|
53598
|
+
}
|
|
53599
|
+
}
|
|
53600
|
+
}
|
|
53601
|
+
}
|
|
53602
|
+
for (const rule of policy.rules) {
|
|
53603
|
+
const guardrail = dsl.guardrails[rule.guardrail];
|
|
53604
|
+
if (!guardrail) continue;
|
|
53605
|
+
const scope = guardrail.scope ?? {};
|
|
53606
|
+
entries.push({
|
|
53607
|
+
guardrail_id: rule.guardrail,
|
|
53608
|
+
description: guardrail.description,
|
|
53609
|
+
severity: rule.severity,
|
|
53610
|
+
action: rule.action,
|
|
53611
|
+
scoped_agents: scope.agents ?? [],
|
|
53612
|
+
scoped_tasks: scope.tasks ?? [],
|
|
53613
|
+
scoped_workflows: scope.workflows ?? [],
|
|
53614
|
+
scoped_tools: scope.tools ?? [],
|
|
53615
|
+
scoped_artifacts: scope.artifacts ?? [],
|
|
53616
|
+
allow_override: rule.allow_override,
|
|
53617
|
+
override_requires: rule.override_requires ?? [],
|
|
53618
|
+
trigger: bindingTriggers.get(rule.guardrail) ?? null,
|
|
53619
|
+
escalation: rule.escalation ? { target: rule.escalation.target, condition: rule.escalation.condition } : null
|
|
53620
|
+
});
|
|
53621
|
+
}
|
|
53622
|
+
return entries;
|
|
53516
53623
|
}
|
|
53517
53624
|
function buildTaskContext(dsl, taskId) {
|
|
53518
53625
|
const taskDef = dsl.tasks[taskId];
|
|
@@ -54567,6 +54674,50 @@ Handlebars.registerHelper(
|
|
|
54567
54674
|
return generateOverviewFlowchart(dsl);
|
|
54568
54675
|
}
|
|
54569
54676
|
);
|
|
54677
|
+
Handlebars.registerHelper(
|
|
54678
|
+
"guardrailCoverageMatrix",
|
|
54679
|
+
function() {
|
|
54680
|
+
const enforcement = this["guardrailEnforcement"];
|
|
54681
|
+
if (!enforcement || enforcement.length === 0) return "";
|
|
54682
|
+
const header = "| Guardrail | Severity | Action | Agents | Tasks | Workflows | Tools | Artifacts | Trigger | Override | Escalation |";
|
|
54683
|
+
const divider = "|-----------|----------|--------|--------|-------|-----------|-------|-----------|---------|----------|------------|";
|
|
54684
|
+
const rows = enforcement.map((e3) => {
|
|
54685
|
+
const agents = e3["scoped_agents"]?.join(", ") || "\u2014";
|
|
54686
|
+
const tasks = e3["scoped_tasks"]?.join(", ") || "\u2014";
|
|
54687
|
+
const workflows = e3["scoped_workflows"]?.join(", ") || "\u2014";
|
|
54688
|
+
const tools = e3["scoped_tools"]?.join(", ") || "\u2014";
|
|
54689
|
+
const artifacts = e3["scoped_artifacts"]?.join(", ") || "\u2014";
|
|
54690
|
+
const trigger = e3["trigger"] || "\u2014";
|
|
54691
|
+
const override = e3["allow_override"] ? "yes" : "no";
|
|
54692
|
+
const esc = e3["escalation"];
|
|
54693
|
+
const escalation = esc ? esc["target"] : "\u2014";
|
|
54694
|
+
return `| ${e3["guardrail_id"]} | ${e3["severity"]} | ${e3["action"]} | ${agents} | ${tasks} | ${workflows} | ${tools} | ${artifacts} | ${trigger} | ${override} | ${escalation} |`;
|
|
54695
|
+
});
|
|
54696
|
+
return [header, divider, ...rows].join("\n");
|
|
54697
|
+
}
|
|
54698
|
+
);
|
|
54699
|
+
Handlebars.registerHelper(
|
|
54700
|
+
"taskGuardrailMatrix",
|
|
54701
|
+
function() {
|
|
54702
|
+
const dsl = this["dsl"];
|
|
54703
|
+
const enforcement = this["guardrailEnforcement"];
|
|
54704
|
+
if (!dsl || !enforcement || enforcement.length === 0) return "";
|
|
54705
|
+
const taskIds = Object.keys(dsl.tasks);
|
|
54706
|
+
if (taskIds.length === 0) return "";
|
|
54707
|
+
const guardrailIds = enforcement.map((e3) => e3["guardrail_id"]);
|
|
54708
|
+
const header = `| Task | ${guardrailIds.join(" | ")} |`;
|
|
54709
|
+
const divider = `|------|${guardrailIds.map(() => "------").join("|")}|`;
|
|
54710
|
+
const rows = taskIds.map((taskId) => {
|
|
54711
|
+
const cells = enforcement.map((e3) => {
|
|
54712
|
+
const scopedTasks = e3["scoped_tasks"];
|
|
54713
|
+
if (scopedTasks.length > 0 && !scopedTasks.includes(taskId)) return "n/a";
|
|
54714
|
+
return e3["action"];
|
|
54715
|
+
});
|
|
54716
|
+
return `| ${taskId} | ${cells.join(" | ")} |`;
|
|
54717
|
+
});
|
|
54718
|
+
return [header, divider, ...rows].join("\n");
|
|
54719
|
+
}
|
|
54720
|
+
);
|
|
54570
54721
|
function getDslSection(dsl, context) {
|
|
54571
54722
|
const sectionMap = {
|
|
54572
54723
|
agent: dsl.agents,
|
|
@@ -54631,13 +54782,13 @@ async function removeIfExists(filePath) {
|
|
|
54631
54782
|
} catch {
|
|
54632
54783
|
}
|
|
54633
54784
|
}
|
|
54634
|
-
async function renderFromConfig(dsl, renderTargets) {
|
|
54785
|
+
async function renderFromConfig(dsl, renderTargets, options) {
|
|
54635
54786
|
const outputFiles = [];
|
|
54636
54787
|
for (const target of renderTargets) {
|
|
54637
54788
|
const templateContent = await loadTemplate(target.template);
|
|
54638
54789
|
const compiled = Handlebars.compile(templateContent, { noEscape: false });
|
|
54639
54790
|
if (target.context === "system") {
|
|
54640
|
-
const ctx = buildSystemContext(dsl);
|
|
54791
|
+
const ctx = buildSystemContext(dsl, options);
|
|
54641
54792
|
const output = compiled(ctx);
|
|
54642
54793
|
if (target.skip_empty && isEffectivelyEmpty(output)) {
|
|
54643
54794
|
await removeIfExists(target.output);
|
|
@@ -54686,13 +54837,13 @@ function checkExpectedVsExisting(expected, outputPath, skipEmpty, diffs) {
|
|
|
54686
54837
|
diffs.push(outputPath);
|
|
54687
54838
|
});
|
|
54688
54839
|
}
|
|
54689
|
-
async function checkDriftFromConfig(dsl, renderTargets) {
|
|
54840
|
+
async function checkDriftFromConfig(dsl, renderTargets, options) {
|
|
54690
54841
|
const diffs = [];
|
|
54691
54842
|
for (const target of renderTargets) {
|
|
54692
54843
|
const templateContent = await loadTemplate(target.template);
|
|
54693
54844
|
const compiled = Handlebars.compile(templateContent, { noEscape: false });
|
|
54694
54845
|
if (target.context === "system") {
|
|
54695
|
-
const ctx = buildSystemContext(dsl);
|
|
54846
|
+
const ctx = buildSystemContext(dsl, options);
|
|
54696
54847
|
const expected = compiled(ctx);
|
|
54697
54848
|
await checkExpectedVsExisting(expected, target.output, target.skip_empty, diffs);
|
|
54698
54849
|
} else {
|
|
@@ -54730,10 +54881,19 @@ var renderCommand = new Command4("render").description("Render resolved DSL with
|
|
|
54730
54881
|
);
|
|
54731
54882
|
process.exit(1);
|
|
54732
54883
|
}
|
|
54884
|
+
let renderOptions;
|
|
54885
|
+
if (config.bindings.length > 0) {
|
|
54886
|
+
const loadedBindings = await loadBindings(config.bindings);
|
|
54887
|
+
renderOptions = {
|
|
54888
|
+
loadedBindings,
|
|
54889
|
+
activeGuardrailPolicy: config.activeGuardrailPolicy
|
|
54890
|
+
};
|
|
54891
|
+
}
|
|
54733
54892
|
if (opts.check) {
|
|
54734
54893
|
const drift = await checkDriftFromConfig(
|
|
54735
54894
|
schemaResult.data,
|
|
54736
|
-
config.renders
|
|
54895
|
+
config.renders,
|
|
54896
|
+
renderOptions
|
|
54737
54897
|
);
|
|
54738
54898
|
if (drift.hasDrift) {
|
|
54739
54899
|
process.stderr.write("Drift detected in the following files:\n");
|
|
@@ -54749,7 +54909,8 @@ var renderCommand = new Command4("render").description("Render resolved DSL with
|
|
|
54749
54909
|
} else {
|
|
54750
54910
|
const files = await renderFromConfig(
|
|
54751
54911
|
schemaResult.data,
|
|
54752
|
-
config.renders
|
|
54912
|
+
config.renders,
|
|
54913
|
+
renderOptions
|
|
54753
54914
|
);
|
|
54754
54915
|
if (!opts.quiet) {
|
|
54755
54916
|
process.stdout.write(`Rendered ${files.length} file(s):
|