kibi-cli 2.16.0 → 2.17.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.
Files changed (37) hide show
  1. package/dist/cli-protocol.d.ts.map +1 -1
  2. package/dist/cli-protocol.js +13 -0
  3. package/dist/cli-register-proof.d.ts +6 -0
  4. package/dist/cli-register-proof.d.ts.map +1 -1
  5. package/dist/cli-register-proof.js +37 -1
  6. package/dist/operations/modeling/predicate-applicability.d.ts.map +1 -1
  7. package/dist/operations/modeling/predicate-applicability.js +7 -1
  8. package/dist/operations/modeling/predicate-utils.d.ts.map +1 -1
  9. package/dist/operations/modeling/predicate-utils.js +2 -3
  10. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  11. package/dist/operations/planning/apply-plan.js +23 -7
  12. package/dist/operations/planning/migration-refusal.d.ts +14 -0
  13. package/dist/operations/planning/migration-refusal.d.ts.map +1 -0
  14. package/dist/operations/planning/migration-refusal.js +17 -0
  15. package/dist/operations/semantic-advisor/shared.d.ts +7 -0
  16. package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
  17. package/dist/operations/semantic-advisor/shared.js +22 -3
  18. package/dist/proof/inspect.d.ts +2 -0
  19. package/dist/proof/inspect.d.ts.map +1 -1
  20. package/dist/proof/integration-plan.d.ts +2 -1
  21. package/dist/proof/integration-plan.d.ts.map +1 -1
  22. package/dist/proof/integration-plan.js +30 -13
  23. package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -1
  24. package/dist/public/impact/coverage-depth-quality.js +29 -1
  25. package/dist/public/operations/discovery-executors.js +5 -5
  26. package/dist/public/operations/result-envelope.d.ts.map +1 -1
  27. package/dist/public/operations/result-envelope.js +23 -3
  28. package/dist/public/skills/kibi-usage/SKILL.md +8 -2
  29. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +3 -3
  30. package/dist/public/skills/kibi-usage/resources/proof.md +21 -3
  31. package/package.json +2 -2
  32. package/src/public/impact/coverage-depth-quality.ts +46 -1
  33. package/src/public/operations/discovery-executors.ts +5 -5
  34. package/src/public/operations/result-envelope.ts +31 -3
  35. package/src/public/skills/kibi-usage/SKILL.md +8 -2
  36. package/src/public/skills/kibi-usage/resources/fact-lanes.md +3 -3
  37. package/src/public/skills/kibi-usage/resources/proof.md +21 -3
@@ -1 +1 @@
1
- {"version":3,"file":"cli-protocol.d.ts","sourceRoot":"","sources":["../src/cli-protocol.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG7D,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,mBAAmB,CAAC;AAS3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAE7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAMpE,MAAM,MAAM,UAAU,GAAG,gBAAgB,GAAG;IAC1C,QAAQ,CAAC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CACpD,CAAC;AAGF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,iBAAS,WAAW,CAClB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,GAAG,cAAc,EAClC,IAAI,CAAC,EAAE;IACL,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,EACD,SAAS,UAAO,EAChB,OAAO,CAAC,EAAE,OAAO,GAChB,iBAAiB,CAwBnB;AA4BD;;;;GAIG;AAEH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EACvC,KAAK,EAAE,OAAO,GACb,iBAAiB,CAWnB;AAGD,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,iBAAiB,CAAC,CAwE5B"}
1
+ {"version":3,"file":"cli-protocol.d.ts","sourceRoot":"","sources":["../src/cli-protocol.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG7D,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,mBAAmB,CAAC;AAS3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAE7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAMpE,MAAM,MAAM,UAAU,GAAG,gBAAgB,GAAG;IAC1C,QAAQ,CAAC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CACpD,CAAC;AAGF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,iBAAS,WAAW,CAClB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,GAAG,cAAc,EAClC,IAAI,CAAC,EAAE;IACL,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,EACD,SAAS,UAAO,EAChB,OAAO,CAAC,EAAE,OAAO,GAChB,iBAAiB,CAwBnB;AA4BD;;;;GAIG;AAEH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EACvC,KAAK,EAAE,OAAO,GACb,iBAAiB,CAWnB;AAGD,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,iBAAiB,CAAC,CAqF5B"}
@@ -78,6 +78,19 @@ export async function executeOperation(catalogName, input, context) {
78
78
  if (!protocolValid(spec, envelope)) {
79
79
  return errorResult(catalogName, new OperationError("PROTOCOL_VALIDATION_FAILED", "Operation produced a result that does not satisfy its generated output contract."), spec);
80
80
  }
81
+ // A result the operation itself reports as an error (a rejected
82
+ // bootstrap, a refused migration plan) exits non-zero like any other.
83
+ if (envelope.status === "error") {
84
+ return {
85
+ exitCode: 1,
86
+ stdout: `${JSON.stringify(envelope)}\n`,
87
+ ...(envelope.error
88
+ ? {
89
+ stderr: `Error [${envelope.error.code}]: ${envelope.error.message}\n`,
90
+ }
91
+ : {}),
92
+ };
93
+ }
81
94
  return { exitCode: 0, stdout: `${JSON.stringify(envelope)}\n` };
82
95
  }
83
96
  catch (error) {
@@ -1,3 +1,9 @@
1
1
  import type { Command } from "commander";
2
2
  export declare function registerProofCommand(program: Command): void;
3
+ /**
4
+ * `--command` for `kibi proof inspect`: a JSON array of argv strings, or
5
+ * space-separated words (no shell quoting; Kibi runs integrations with
6
+ * `shell: false`).
7
+ */
8
+ export declare function parseCommandOption(value: string): readonly string[];
3
9
  //# sourceMappingURL=cli-register-proof.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-register-proof.d.ts","sourceRoot":"","sources":["../src/cli-register-proof.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6L3D"}
1
+ {"version":3,"file":"cli-register-proof.d.ts","sourceRoot":"","sources":["../src/cli-register-proof.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2M3D;AAED;;;;GAIG;AAEH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAwBnE"}
@@ -41,9 +41,16 @@ export function registerProofCommand(program) {
41
41
  .command("inspect")
42
42
  .description("Detect test infrastructure, propose a proof integration and return the reviewed plan that writes .kb/proof/integrations.json")
43
43
  .option("--update <id>", "Plan adding or replacing this integration in an existing .kb/proof/integrations.json")
44
+ .option("--command <command>", 'Command the proposed integration runs instead of the detected one: space-separated words, or a JSON array of argv strings (e.g. \'["npx","vitest","run","tests/e2e"]\')')
44
45
  .option("--json", "Emit structured JSON", false)
45
46
  .action(withExitCode(async (options) => {
46
- const result = await (await import("./proof/inspect.js")).inspectProofEnvironment(process.cwd(), options.update === undefined ? {} : { update: options.update });
47
+ const command = options.command === undefined
48
+ ? undefined
49
+ : parseCommandOption(options.command);
50
+ const result = await (await import("./proof/inspect.js")).inspectProofEnvironment(process.cwd(), {
51
+ ...(options.update === undefined ? {} : { update: options.update }),
52
+ ...(command === undefined ? {} : { command }),
53
+ });
47
54
  if (options.json) {
48
55
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
49
56
  return undefined;
@@ -109,6 +116,33 @@ export function registerProofCommand(program) {
109
116
  });
110
117
  }));
111
118
  }
119
+ /**
120
+ * `--command` for `kibi proof inspect`: a JSON array of argv strings, or
121
+ * space-separated words (no shell quoting; Kibi runs integrations with
122
+ * `shell: false`).
123
+ */
124
+ // implements REQ-kibi-verification-evidence-contract
125
+ export function parseCommandOption(value) {
126
+ const trimmed = value.trim();
127
+ if (trimmed.startsWith("[")) {
128
+ let parsed;
129
+ try {
130
+ parsed = JSON.parse(trimmed);
131
+ }
132
+ catch {
133
+ throw new Error("--command must be a JSON array of argv strings or space-separated words.");
134
+ }
135
+ if (!Array.isArray(parsed) ||
136
+ parsed.length === 0 ||
137
+ !parsed.every((entry) => typeof entry === "string" && entry !== ""))
138
+ throw new Error("--command must be a JSON array of non-empty argv strings.");
139
+ return parsed;
140
+ }
141
+ const words = trimmed.split(/\s+/).filter((word) => word !== "");
142
+ if (words.length === 0)
143
+ throw new Error("--command must not be empty.");
144
+ return words;
145
+ }
112
146
  function renderInspection(result) {
113
147
  const inspection = result;
114
148
  const lines = ["Proof environment", ""];
@@ -145,6 +179,8 @@ function renderInspection(result) {
145
179
  if (inspection.integrationPlan) {
146
180
  const plan = inspection.integrationPlan;
147
181
  lines.push(`Integration plan: ${plan.planHash.slice(0, 12)} (actions: ${plan.actions.map((action) => action.id).join(", ")}); rerun with --json for the plan to pass to kb_apply_plan.`);
182
+ if (inspection.integrationPlanReason)
183
+ lines.push(inspection.integrationPlanReason);
148
184
  }
149
185
  else if (inspection.integrationPlanReason) {
150
186
  lines.push("");
@@ -1 +1 @@
1
- {"version":3,"file":"predicate-applicability.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-applicability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,uBAAuB,CAAC;AAG/B,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAqW1C,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE,MAAM,GACX,qBAAqB,CA2EvB"}
1
+ {"version":3,"file":"predicate-applicability.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-applicability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,uBAAuB,CAAC;AAG/B,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C,eAAO,MAAM,qBAAqB,OAAO,CAAC;AA2W1C,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE,MAAM,GACX,qBAAqB,CA2EvB"}
@@ -96,7 +96,13 @@ const INTENT_RULES = {
96
96
  permission_rule: {
97
97
  allOf: [
98
98
  [/only|may|can|allowed|denied|forbidden|must\s+not|cannot/i],
99
- [/delete|export|access|read|write|update|remove|manage|permission/i],
99
+ [
100
+ /delete|export|access|read|write|update|remove|manage|permission/i,
101
+ // A common action verb counts only right after the permission modal
102
+ // ("must not publish", "may approve"), so a quota such as "uploads
103
+ // must not exceed 10 MB" does not read as a permission.
104
+ /\b(?:may|can|cannot|can't|must\s+(?:not|never)|never|(?:is|are)\s+(?:not\s+)?(?:allowed|permitted|forbidden)\s+to)\s+(?:only\s+)?(?:publish|unpublish|approve|assign|view|see|edit|modify|create|submit|share|invite|upload|download|comment|post|complete|archive|restore|grant|revoke|purchase|cancel|execute|invoke|configure|moderate|ban|join|book|reserve|transfer|close|reopen|merge)\b/i,
105
+ ],
100
106
  ],
101
107
  },
102
108
  resource_constraint: {
@@ -1 +1 @@
1
- {"version":3,"file":"predicate-utils.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-utils.ts"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQlD;AAGD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,GAAG,SAAS,CAGpB;AAGD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,CAIR;AAGD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAM5D;AAGD,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM1C;AAGD,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAO9D;AAED;;;;;;GAMG;AAEH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,WAAW,CAAC,EAAE,QAAQ,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC,CAAC,GACD,MAAM,CAeR;AAGD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASjD;AAGD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO/C;AAGD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjD;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAGD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAGD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAOT;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMtD;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAclD;AAGD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAGD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM9C;AAGD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9C;AAGD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C"}
1
+ {"version":3,"file":"predicate-utils.d.ts","sourceRoot":"","sources":["../../../src/operations/modeling/predicate-utils.ts"],"names":[],"mappings":"AAMA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQlD;AAGD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,GAAG,SAAS,CAGpB;AAGD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,CAIR;AAGD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAM5D;AAGD,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM1C;AAGD,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAO9D;AAED;;;;;;GAMG;AAEH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,WAAW,CAAC,EAAE,QAAQ,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC,CAAC,GACD,MAAM,CAeR;AAGD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASjD;AAGD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO/C;AAGD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAGD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAGD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAOT;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMtD;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAclD;AAGD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAGD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM9C;AAGD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9C;AAGD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C"}
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { singularizeToken } from "../semantic-advisor/shared.js";
2
3
  const DEFAULT_MIN_SCORE = 0.35;
3
4
  // implements REQ-mcp-suggest-predicates
4
5
  export function normalizeText(text) {
@@ -114,9 +115,7 @@ export function normalizePredicateToken(value) {
114
115
  }
115
116
  // implements REQ-mcp-suggest-predicates
116
117
  export function singularize(value) {
117
- if (["changes", "status", "results"].includes(value))
118
- return value;
119
- return value.endsWith("s") && value.length > 3 ? value.slice(0, -1) : value;
118
+ return singularizeToken(value, ["changes", "status", "results"]);
120
119
  }
121
120
  // implements REQ-mcp-suggest-predicates
122
121
  export function normalizeSubjectKey(value) {
@@ -1 +1 @@
1
- {"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAEV,gBAAgB,EAGjB,MAAM,0CAA0C,CAAC;AASlD,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AA+D/B,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EAGhB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAG/B,eAAO,MAAM,yBAAyB,EAAG,2BAAoC,CAAC;AA6G9E,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAMN;AAED,wBAAgB,mCAAmC,CACjD,SAAS,EAAE,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,EACnE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,IAAI,CAaN;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,SAAS,cAAoB,GAC5B,eAAe,EAAE,CA2CnB;AA8zBD;;;;;;GAMG;AAEH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAiCf;AAyqDD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC;IACT,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,iBAAiB,EAAE,eAAe,CAAC;CACpC,CAAC,CAkDD"}
1
+ {"version":3,"file":"apply-plan.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/apply-plan.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAEV,gBAAgB,EAGjB,MAAM,0CAA0C,CAAC;AASlD,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AAgE/B,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EAGhB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAG/B,eAAO,MAAM,yBAAyB,EAAG,2BAAoC,CAAC;AA6G9E,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAMN;AAED,wBAAgB,mCAAmC,CACjD,SAAS,EAAE,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,EACnE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,IAAI,CAaN;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,SAAS,cAAoB,GAC5B,eAAe,EAAE,CA2CnB;AA8zBD;;;;;;GAMG;AAEH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAiCf;AAyqDD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC;IACT,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,iBAAiB,EAAE,eAAe,CAAC;CACpC,CAAC,CAkDD"}
@@ -24,6 +24,7 @@ import { canonicalSourcePath, renderSourceDocument, } from "../mutation/source-a
24
24
  import { assertRelationshipShardContained, executeUpsert, validateUpsertForCommit, } from "../mutation/upsert.js";
25
25
  import { acquireWorkspaceMutationLock, releaseWorkspaceMutationLock, } from "../mutation/workspace-mutation-lock.js";
26
26
  import { compilePlanHash, isBlockingWitness, parseWhatIfAnalysis, planWhatIfGoal, } from "./compile-intent.js";
27
+ import { MigrationActionRefusedError } from "./migration-refusal.js";
27
28
  // implements REQ-kibi-change-to-proof-plan-compiler-v2
28
29
  export const PLAN_APPLY_RESULT_VERSION = "kibi.plan-apply-result.v1";
29
30
  const ENTITY_TYPES = new Set([
@@ -2121,6 +2122,9 @@ async function applyMigrationPlan(args, context, onCommitted = () => undefined)
2121
2122
  }
2122
2123
  const results = [];
2123
2124
  let failed = false;
2125
+ // Failures that changed nothing (a precondition refused the action).
2126
+ let refusals = 0;
2127
+ let failures = 0;
2124
2128
  for (const action of actions) {
2125
2129
  if (failed) {
2126
2130
  results.push({
@@ -2141,6 +2145,9 @@ async function applyMigrationPlan(args, context, onCommitted = () => undefined)
2141
2145
  }
2142
2146
  catch (error) {
2143
2147
  failed = true;
2148
+ failures += 1;
2149
+ if (error instanceof MigrationActionRefusedError)
2150
+ refusals += 1;
2144
2151
  results.push({
2145
2152
  actionId: action.id,
2146
2153
  outcome: "failed",
@@ -2190,10 +2197,15 @@ async function applyMigrationPlan(args, context, onCommitted = () => undefined)
2190
2197
  });
2191
2198
  }
2192
2199
  }
2200
+ // refused: nothing was applied and every failure was a refusal, so the
2201
+ // workspace is as it was; reconciliation_required: an action failed
2202
+ // without being refused, so its effect must be inspected.
2193
2203
  const outcome = failed
2194
2204
  ? results.some((result) => result.outcome === "applied")
2195
2205
  ? "partially_applied"
2196
- : "reconciliation_required"
2206
+ : failures > 0 && refusals === failures
2207
+ ? "refused"
2208
+ : "reconciliation_required"
2197
2209
  : "applied";
2198
2210
  const kbState = finalStatus.branchAttachment?.migrationRequired
2199
2211
  ? "legacy_compat"
@@ -2237,7 +2249,7 @@ async function applyMigrationPlan(args, context, onCommitted = () => undefined)
2237
2249
  closeout: {
2238
2250
  taskOutcome: outcome === "applied"
2239
2251
  ? "complete"
2240
- : outcome === "reconciliation_required"
2252
+ : outcome === "reconciliation_required" || outcome === "refused"
2241
2253
  ? "blocked"
2242
2254
  : "interim",
2243
2255
  kbState,
@@ -2250,7 +2262,11 @@ async function applyMigrationPlan(args, context, onCommitted = () => undefined)
2250
2262
  content: [
2251
2263
  {
2252
2264
  type: "text",
2253
- text: `${outcome === "applied" ? "Applied" : "Stopped after"} migration plan ${args.plan.planHash.slice(0, 12)}.`,
2265
+ text: outcome === "applied"
2266
+ ? `Applied migration plan ${args.plan.planHash.slice(0, 12)}.`
2267
+ : outcome === "refused"
2268
+ ? `Refused migration plan ${args.plan.planHash.slice(0, 12)}; nothing was changed.`
2269
+ : `Stopped after migration plan ${args.plan.planHash.slice(0, 12)}.`,
2254
2270
  },
2255
2271
  ],
2256
2272
  structuredContent: payload,
@@ -2327,7 +2343,7 @@ async function applyMigrationAction(action, context) {
2327
2343
  await applySchema6MigrationAction(action, context);
2328
2344
  return;
2329
2345
  }
2330
- throw new Error(`Migration action '${action.code}' has no automatic executor.`);
2346
+ throw new MigrationActionRefusedError(`Migration action '${action.code}' has no automatic executor.`);
2331
2347
  }
2332
2348
  }
2333
2349
  /**
@@ -2338,10 +2354,10 @@ async function applyMigrationAction(action, context) {
2338
2354
  async function applyPredicateSchemaAlignment(action, context) {
2339
2355
  const invocation = action.invocation;
2340
2356
  if (invocation.kind !== "operation" || invocation.name !== "kb_upsert")
2341
- throw new Error("Predicate schema alignment requires its planned kb_upsert invocation.");
2357
+ throw new MigrationActionRefusedError("Predicate schema alignment requires its planned kb_upsert invocation.");
2342
2358
  const input = asUpsert(invocation.input);
2343
2359
  if (input.type !== "fact")
2344
- throw new Error("Predicate schema alignment only rewrites fact entities.");
2360
+ throw new MigrationActionRefusedError("Predicate schema alignment only rewrites fact entities.");
2345
2361
  const prolog = context.prolog ?? (await context.ensureProlog?.());
2346
2362
  if (!prolog)
2347
2363
  throw new Error("Predicate schema alignment requires a Prolog runtime.");
@@ -2363,6 +2379,6 @@ async function applyPredicateSchemaAlignment(action, context) {
2363
2379
  typeof rewrite.index === "number" &&
2364
2380
  currentArgs[rewrite.index] === rewrite.from);
2365
2381
  if (!unchanged)
2366
- throw new Error(`Predicate fact ${input.id} changed since planning; rerun kibi check and approve the new plan.`);
2382
+ throw new MigrationActionRefusedError(`Predicate fact ${input.id} changed since planning; rerun kibi check and approve the new plan.`);
2367
2383
  await executeUpsert(input, operationContext);
2368
2384
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * A migration action executor throws this when it refuses to run: a
3
+ * precondition failed before it changed anything (the planned file already
4
+ * exists, a source changed since planning, the action is malformed). An
5
+ * application whose only failures are refusals and that applied nothing
6
+ * reports `outcome: "refused"`, not `reconciliation_required`: there is
7
+ * nothing to reconcile.
8
+ */
9
+ export declare class MigrationActionRefusedError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ /** Error code of the envelope for a migration plan that was refused. */
13
+ export declare const MIGRATION_PLAN_REFUSED: "MIGRATION_PLAN_REFUSED";
14
+ //# sourceMappingURL=migration-refusal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-refusal.d.ts","sourceRoot":"","sources":["../../../src/operations/planning/migration-refusal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,qBAAa,2BAA4B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAED,wEAAwE;AACxE,eAAO,MAAM,sBAAsB,EAAG,wBAAiC,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A migration action executor throws this when it refuses to run: a
3
+ * precondition failed before it changed anything (the planned file already
4
+ * exists, a source changed since planning, the action is malformed). An
5
+ * application whose only failures are refusals and that applied nothing
6
+ * reports `outcome: "refused"`, not `reconciliation_required`: there is
7
+ * nothing to reconcile.
8
+ */
9
+ // implements REQ-kibi-change-to-proof-plan-compiler-v2
10
+ export class MigrationActionRefusedError extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "MigrationActionRefusedError";
14
+ }
15
+ }
16
+ /** Error code of the envelope for a migration plan that was refused. */
17
+ export const MIGRATION_PLAN_REFUSED = "MIGRATION_PLAN_REFUSED";
@@ -19,6 +19,13 @@ export declare function statementOf(payload: Payload): string;
19
19
  export declare function sourceOf(payload: Payload): string;
20
20
  export declare function normalizeKey(value: string): string;
21
21
  export declare function normalizePredicateToken(value: string): string;
22
+ /**
23
+ * Singularize one lowercase key token: `-ches`/`-shes`/`-sses`/`-xes` drop
24
+ * "es", `-ies` becomes "y", and any other trailing "s" is dropped unless the
25
+ * token ends in "ss", "us" or "is" ("access", "status", "analysis"). Tokens of
26
+ * three letters or fewer and the `keep` exceptions are returned unchanged.
27
+ */
28
+ export declare function singularizeToken(value: string, keep?: readonly string[]): string;
22
29
  export declare function singularize(value: string): string;
23
30
  export declare function normalizeSubjectKey(value: string): string;
24
31
  export declare function relationship(from: string, to: string, type: string): Record<string, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/shared.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,0BAA0B,EAE1B,2BAA2B,EAC5B,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AACxD,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AACvE,eAAO,MAAM,0BAA0B,+BAA+B,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAAG,UAAU,GAAG,OAAO,CAAC;AAGzE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAElD;AAGD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CASnD;AAGD,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAKpD;AAGD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG;IAClD,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAmBA;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,GACf,SAAS,MAAM,EAAE,GAAG,SAAS,CAO/B;AAGD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAGD,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEtE;AAGD,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAEpD;AAGD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAIjD;AAGD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlD;AAGD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjD;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB;AAGD,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,2BAA2B,EAClC,SAAS,EAAE,MAAM,EACjB,UAAU,SAAM,GACf,0BAA0B,CA0F5B;AAGD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,QAAQ,GAAG,MAAiB,GACrC,0BAA0B,CA+C5B"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/operations/semantic-advisor/shared.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,0BAA0B,EAE1B,2BAA2B,EAC5B,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AACxD,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AACvE,eAAO,MAAM,0BAA0B,+BAA+B,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAAG,UAAU,GAAG,OAAO,CAAC;AAGzE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAElD;AAGD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CASnD;AAGD,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAKpD;AAGD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG;IAClD,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAmBA;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,GACf,SAAS,MAAM,EAAE,GAAG,SAAS,CAO/B;AAGD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAGD,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEtE;AAGD,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAEpD;AAGD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAIjD;AAGD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlD;AAGD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAID;;;;;GAKG;AAEH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,SAAS,MAAM,EAAO,GAC3B,MAAM,CAUR;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB;AAGD,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,2BAA2B,EAClC,SAAS,EAAE,MAAM,EACjB,UAAU,SAAM,GACf,0BAA0B,CA0F5B;AAGD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,QAAQ,GAAG,MAAiB,GACrC,0BAA0B,CA+C5B"}
@@ -90,11 +90,30 @@ export function normalizePredicateToken(value) {
90
90
  .replace(/[^A-Za-z0-9_-]+/g, "_")
91
91
  .replace(/^_+|_+$/g, "");
92
92
  }
93
+ const ES_PLURAL_SUFFIXES = ["ches", "shes", "sses", "xes"];
94
+ /**
95
+ * Singularize one lowercase key token: `-ches`/`-shes`/`-sses`/`-xes` drop
96
+ * "es", `-ies` becomes "y", and any other trailing "s" is dropped unless the
97
+ * token ends in "ss", "us" or "is" ("access", "status", "analysis"). Tokens of
98
+ * three letters or fewer and the `keep` exceptions are returned unchanged.
99
+ */
93
100
  // implements REQ-mcp-semantic-advisor-preflight
94
- export function singularize(value) {
95
- if (["status", "results"].includes(value))
101
+ export function singularizeToken(value, keep = []) {
102
+ if (value.length <= 3 || keep.includes(value))
103
+ return value;
104
+ if (value.endsWith("ies") && value.length > 4) {
105
+ return `${value.slice(0, -3)}y`;
106
+ }
107
+ if (ES_PLURAL_SUFFIXES.some((suffix) => value.endsWith(suffix))) {
108
+ return value.slice(0, -2);
109
+ }
110
+ if (!value.endsWith("s") || /(?:ss|us|is)$/.test(value))
96
111
  return value;
97
- return value.endsWith("s") && value.length > 3 ? value.slice(0, -1) : value;
112
+ return value.slice(0, -1);
113
+ }
114
+ // implements REQ-mcp-semantic-advisor-preflight
115
+ export function singularize(value) {
116
+ return singularizeToken(value, ["status", "results"]);
98
117
  }
99
118
  // implements REQ-mcp-semantic-advisor-preflight
100
119
  export function normalizeSubjectKey(value) {
@@ -23,6 +23,8 @@ export type ProofInspection = Readonly<{
23
23
  export type ProofInspectionOptions = Readonly<{
24
24
  /** Plan a replacement of this existing integration id. */
25
25
  update?: string;
26
+ /** Command (argv) the proposed integration runs instead of the detected one. */
27
+ command?: readonly string[];
26
28
  }>;
27
29
  export declare function directoryEntriesMatch(directory: string, regex: RegExp): boolean;
28
30
  export declare function inspectProofEnvironment(root: string, options?: ProofInspectionOptions): ProofInspection;
@@ -1 +1 @@
1
- {"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/proof/inspect.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,uBAAuB,CAAC;AAM/B,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,qEAAqE;IACrE,mBAAmB,EAAE,mBAAmB,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;IAC/D,6EAA6E;IAC7E,gBAAgB,EAAE,mBAAmB,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC;IACjE;;;OAGG;IACH,eAAe,EAAE,aAAa,GAAG,IAAI,CAAC;IACtC,sDAAsD;IACtD,qBAAqB,EAAE,MAAM,CAAC;CAC/B,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAwIH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAMT;AA2BD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAgGjB"}
1
+ {"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/proof/inspect.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,uBAAuB,CAAC;AAM/B,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,qEAAqE;IACrE,mBAAmB,EAAE,mBAAmB,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;IAC/D,6EAA6E;IAC7E,gBAAgB,EAAE,mBAAmB,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC;IACjE;;;OAGG;IACH,eAAe,EAAE,aAAa,GAAG,IAAI,CAAC;IACtC,sDAAsD;IACtD,qBAAqB,EAAE,MAAM,CAAC;CAC/B,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B,CAAC,CAAC;AAwIH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAMT;AA2BD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAgGjB"}
@@ -33,7 +33,7 @@ export type ProofIntegrationPlanResult = Readonly<{
33
33
  * (aggregate-run provenance), so it works for every runner without a
34
34
  * reporter; a native producer can replace it later through an update plan.
35
35
  */
36
- export declare function proposeProofIntegration(root: string, overrideId?: string): ProposedIntegration | null;
36
+ export declare function proposeProofIntegration(root: string, overrideId?: string, overrideCommand?: readonly string[]): ProposedIntegration | null;
37
37
  /**
38
38
  * Build the hash-bound `kibi.migration-plan.v2` that writes
39
39
  * `.kb/proof/integrations.json` through Kibi. Without `update`, a plan is
@@ -44,6 +44,7 @@ export declare function proposeProofIntegration(root: string, overrideId?: strin
44
44
  */
45
45
  export declare function buildProofIntegrationPlan(root: string, options?: Readonly<{
46
46
  update?: string;
47
+ command?: readonly string[];
47
48
  }>): ProofIntegrationPlanResult;
48
49
  /**
49
50
  * Apply one reviewed `proof_integration_configure` action: write
@@ -1 +1 @@
1
- {"version":3,"file":"integration-plan.d.ts","sourceRoot":"","sources":["../../src/proof/integration-plan.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,aAAa,EAGnB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,sBAAsB,EAEvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEL,KAAK,gBAAgB,EAGtB,MAAM,mBAAmB,CAAC;AAE3B,mEAAmE;AACnE,eAAO,MAAM,2BAA2B,gCAAgC,CAAC;AAqCzE,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,WAAW,EAAE,gBAAgB,CAAC;IAC9B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,gBAAgB,EAAE,QAAQ,CAAC;QACzB,OAAO,EAAE,OAAO,sBAAsB,CAAC;QACvC,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,aAAa,CAAC;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACtE,cAAc,EAAE,4BAA4B,CAAC;KAC9C,CAAC,CAAC;CACJ,CAAC,CAAC;AAEH,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,0EAA0E;IAC1E,IAAI,EAAE,aAAa,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAyGH;;;;;;;GAOG;AAEH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,GAClB,mBAAmB,GAAG,IAAI,CA8B5B;AAsCD;;;;;;;GAOG;AAEH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,QAAQ,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAM,GAC1C,0BAA0B,CAiE5B;AAMD;;;;;;;GAOG;AAEH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,MAAM,GACX,IAAI,CAyDN"}
1
+ {"version":3,"file":"integration-plan.d.ts","sourceRoot":"","sources":["../../src/proof/integration-plan.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,aAAa,EAGnB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,sBAAsB,EAEvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEL,KAAK,gBAAgB,EAGtB,MAAM,mBAAmB,CAAC;AAE3B,mEAAmE;AACnE,eAAO,MAAM,2BAA2B,gCAAgC,CAAC;AAqCzE,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,WAAW,EAAE,gBAAgB,CAAC;IAC9B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,gBAAgB,EAAE,QAAQ,CAAC;QACzB,OAAO,EAAE,OAAO,sBAAsB,CAAC;QACvC,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,aAAa,CAAC;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACtE,cAAc,EAAE,4BAA4B,CAAC;KAC9C,CAAC,CAAC;CACJ,CAAC,CAAC;AAEH,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,0EAA0E;IAC1E,IAAI,EAAE,aAAa,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAoHH;;;;;;;GAOG;AAEH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,EACnB,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,GAClC,mBAAmB,GAAG,IAAI,CAoC5B;AAsCD;;;;;;;GAOG;AAEH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,QAAQ,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAM,GACvE,0BAA0B,CAiE5B;AAMD;;;;;;;GAOG;AAEH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,MAAM,GACX,IAAI,CAyDN"}
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
3
3
  import path from "node:path";
4
+ import { MigrationActionRefusedError } from "../operations/planning/migration-refusal.js";
4
5
  import { buildMigrationPlan, migrationAction, } from "../public/operations/migration-plan.js";
5
6
  import { PROOF_CONTRACT_VERSION, PROOF_INTEGRATION_VERSION, } from "../public/proof-protocol.js";
6
7
  import { PROOF_INTEGRATIONS_PATH, integrationsPath, proofIntegrationErrors, } from "./integrations.js";
@@ -74,6 +75,16 @@ function packageTestCommand(root) {
74
75
  return ["yarn", "test"];
75
76
  return ["npm", "test"];
76
77
  }
78
+ const PACKAGE_TEST_SCRIPT_RUNNER = "package.json test script";
79
+ /**
80
+ * A command integration is judged as one run. When it runs the package's
81
+ * whole test script, say so: any failing test fails every obligation.
82
+ */
83
+ function wholeRunNote(proposal) {
84
+ if (!proposal.runner.startsWith(PACKAGE_TEST_SCRIPT_RUNNER))
85
+ return "";
86
+ return ` Kibi judges this run as a whole: a failing test anywhere in the package's test script fails every proof obligation that names '${proposal.integration.id}', so unrelated failures prove nothing. To narrow it, pass --command with a command that runs only the proof-bearing tests (kibi proof inspect --update ${proposal.integration.id} --command "<command>" --json once the file exists), or have the command write a kibi.proof-test-report.v1 to the path in KIBI_PROOF_TEST_REPORT so each test is judged by its own steps.`;
87
+ }
77
88
  /** Ordered: the first runner whose marker file exists wins. */
78
89
  const RUNNER_COMMANDS = [
79
90
  {
@@ -144,15 +155,21 @@ const RUNNER_COMMANDS = [
144
155
  * reporter; a native producer can replace it later through an update plan.
145
156
  */
146
157
  // implements REQ-kibi-verification-evidence-contract
147
- export function proposeProofIntegration(root, overrideId) {
158
+ export function proposeProofIntegration(root, overrideId, overrideCommand) {
148
159
  const npmCommand = packageTestCommand(root);
149
- const detected = npmCommand !== null
160
+ const detected = overrideCommand !== undefined && overrideCommand.length > 0
150
161
  ? {
151
- runner: `package.json test script (${npmCommand.join(" ")})`,
162
+ runner: `--command (${overrideCommand.join(" ")})`,
152
163
  id: "unit",
153
- command: npmCommand,
164
+ command: overrideCommand,
154
165
  }
155
- : RUNNER_COMMANDS.find((entry) => entry.files.some((file) => existsSync(path.join(root, file))));
166
+ : npmCommand !== null
167
+ ? {
168
+ runner: `${PACKAGE_TEST_SCRIPT_RUNNER} (${npmCommand.join(" ")})`,
169
+ id: "unit",
170
+ command: npmCommand,
171
+ }
172
+ : RUNNER_COMMANDS.find((entry) => entry.files.some((file) => existsSync(path.join(root, file))));
156
173
  if (detected === undefined)
157
174
  return null;
158
175
  const id = overrideId ?? detected.id;
@@ -235,7 +252,7 @@ export function buildProofIntegrationPlan(root, options = {}) {
235
252
  proposal: null,
236
253
  reason: `${PROOF_INTEGRATIONS_PATH} is not a valid ${PROOF_INTEGRATION_VERSION} file, so Kibi cannot update one integration in it. Run kibi prove for the validation error.`,
237
254
  };
238
- const proposal = proposeProofIntegration(root, update);
255
+ const proposal = proposeProofIntegration(root, update, options.command);
239
256
  if (proposal === null) {
240
257
  return {
241
258
  plan: null,
@@ -273,7 +290,7 @@ export function buildProofIntegrationPlan(root, options = {}) {
273
290
  actions: [action],
274
291
  }),
275
292
  proposal,
276
- reason: `Applying the plan ${update === undefined ? "creates" : "updates"} ${PROOF_INTEGRATIONS_PATH} with the ${proposal.integration.producer} integration '${proposal.integration.id}' (${proposal.integration.command.join(" ")}). Proof-bearing tests then name it in proof_contract.integration and kibi prove runs it.`,
293
+ reason: `Applying the plan ${update === undefined ? "creates" : "updates"} ${PROOF_INTEGRATIONS_PATH} with the ${proposal.integration.producer} integration '${proposal.integration.id}' (${proposal.integration.command.join(" ")}). Proof-bearing tests then name it in proof_contract.integration and kibi prove runs it.${wholeRunNote(proposal)}`,
277
294
  };
278
295
  }
279
296
  function isRecord(value) {
@@ -295,27 +312,27 @@ export function applyProofIntegrationAction(action, root) {
295
312
  (evidence.mode !== "create" && evidence.mode !== "update") ||
296
313
  !isRecord(integration) ||
297
314
  typeof integration.id !== "string")
298
- throw new Error(`Proof integration action '${action.id}' is malformed; rerun kibi proof inspect for a new plan.`);
315
+ throw new MigrationActionRefusedError(`Proof integration action '${action.id}' is malformed; rerun kibi proof inspect for a new plan.`);
299
316
  const planned = isRecord(evidence.sourceHashes) ? evidence.sourceHashes : {};
300
317
  const live = sourceHashes(root);
301
318
  const changed = [
302
319
  ...new Set([...Object.keys(planned), ...Object.keys(live)]),
303
320
  ].filter((file) => planned[file] !== live[file]);
304
321
  if (changed.length > 0)
305
- throw new Error(`Proof integration plan refused: ${changed.join(", ")} changed since planning; rerun kibi proof inspect and review the new plan.`);
322
+ throw new MigrationActionRefusedError(`Proof integration plan refused: ${changed.join(", ")} changed since planning; rerun kibi proof inspect and review the new plan.`);
306
323
  const filePath = integrationsPath(root);
307
324
  let integrations;
308
325
  if (evidence.mode === "create") {
309
326
  if (existsSync(filePath))
310
- throw new Error(`Proof integration plan refused: ${PROOF_INTEGRATIONS_PATH} already exists and this plan only creates it. Run kibi proof inspect --update <integration id> for a plan that adds or replaces one named integration.`);
327
+ throw new MigrationActionRefusedError(`Proof integration plan refused: ${PROOF_INTEGRATIONS_PATH} already exists and this plan only creates it. Run kibi proof inspect --update <integration id> for a plan that adds or replaces one named integration.`);
311
328
  integrations = [integration];
312
329
  }
313
330
  else {
314
331
  if (fileHash(filePath) !== evidence.previousFileHash)
315
- throw new Error(`Proof integration plan refused: ${PROOF_INTEGRATIONS_PATH} changed since planning; rerun kibi proof inspect --update ${integration.id}.`);
332
+ throw new MigrationActionRefusedError(`Proof integration plan refused: ${PROOF_INTEGRATIONS_PATH} changed since planning; rerun kibi proof inspect --update ${integration.id}.`);
316
333
  const current = readIntegrationsFile(root);
317
334
  if (current === null)
318
- throw new Error(`Proof integration plan refused: ${PROOF_INTEGRATIONS_PATH} is not a valid ${PROOF_INTEGRATION_VERSION} file.`);
335
+ throw new MigrationActionRefusedError(`Proof integration plan refused: ${PROOF_INTEGRATIONS_PATH} is not a valid ${PROOF_INTEGRATION_VERSION} file.`);
319
336
  integrations = current.integrations.some((entry) => entry.id === integration.id)
320
337
  ? current.integrations.map((entry) => entry.id === integration.id ? integration : entry)
321
338
  : [...current.integrations, integration];
@@ -323,7 +340,7 @@ export function applyProofIntegrationAction(action, root) {
323
340
  const file = { version: PROOF_INTEGRATION_VERSION, integrations };
324
341
  const errors = proofIntegrationErrors(file);
325
342
  if (errors.length > 0)
326
- throw new Error(`Proof integration plan refused: the result would be invalid (${errors.join("; ")}).`);
343
+ throw new MigrationActionRefusedError(`Proof integration plan refused: the result would be invalid (${errors.join("; ")}).`);
327
344
  mkdirSync(path.dirname(filePath), { recursive: true });
328
345
  const temporary = `${filePath}.${process.pid}.tmp`;
329
346
  writeFileSync(temporary, `${JSON.stringify(file, null, 2)}\n`, "utf8");
@@ -1 +1 @@
1
- {"version":3,"file":"coverage-depth-quality.d.ts","sourceRoot":"","sources":["../../../src/public/impact/coverage-depth-quality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAsIpD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAI1E;AAwID,wBAAgB,qCAAqC,CACnD,eAAe,EAAE,SAAS,gBAAgB,EAAE,EAC5C,kBAAkB,GAAE,WAAW,CAC7B,MAAM,EACN,QAAQ,CAAC;IACP,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C,CAAC,CACS,GACZ,SAAS,iBAAiB,EAAE,CAc9B"}
1
+ {"version":3,"file":"coverage-depth-quality.d.ts","sourceRoot":"","sources":["../../../src/public/impact/coverage-depth-quality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAsIpD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAI1E;AAgLD,wBAAgB,qCAAqC,CACnD,eAAe,EAAE,SAAS,gBAAgB,EAAE,EAC5C,kBAAkB,GAAE,WAAW,CAC7B,MAAM,EACN,QAAQ,CAAC;IACP,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C,CAAC,CACS,GACZ,SAAS,iBAAiB,EAAE,CAmB9B"}
@@ -185,10 +185,38 @@ function coverageForRequirement(result, manifestResults, entities) {
185
185
  scenarioTests,
186
186
  };
187
187
  }
188
+ /**
189
+ * Requirement statuses that keep a requirement in the coverage-depth review.
190
+ * `closed` is reviewed only when tagged `implemented` (the documented
191
+ * "done, with evidence" shape); a plain closed, deprecated or superseded
192
+ * requirement is retired history, not a coverage gap.
193
+ */
194
+ const REVIEWED_REQUIREMENT_STATUSES = new Set([
195
+ "open",
196
+ "in_progress",
197
+ "active",
198
+ "approved",
199
+ ]);
200
+ function supersededRequirementIds(manifestResults) {
201
+ return new Set(manifestResults.flatMap((result) => result.entity.type === "req"
202
+ ? result.relationships.flatMap((relationship) => relationship.type === "supersedes" ? [relationship.to] : [])
203
+ : []));
204
+ }
205
+ // implements REQ-audit-quality-diagnostics-v1
206
+ function isReviewableRequirement(requirement, superseded) {
207
+ if (superseded.has(requirement.id))
208
+ return false;
209
+ const status = requirement.status.trim().toLowerCase();
210
+ if (REVIEWED_REQUIREMENT_STATUSES.has(status))
211
+ return true;
212
+ return (status === "closed" && (requirement.tags ?? []).includes("implemented"));
213
+ }
188
214
  export function createCoverageDepthQualityDiagnostics(manifestResults, proofByRequirement = new Map()) {
189
215
  const entities = entitiesById(manifestResults);
216
+ const superseded = supersededRequirementIds(manifestResults);
190
217
  return manifestResults
191
- .filter((result) => result.entity.type === "req")
218
+ .filter((result) => result.entity.type === "req" &&
219
+ isReviewableRequirement(result.entity, superseded))
192
220
  .flatMap((result) => {
193
221
  const diagnostic = createDiagnostic(coverageForRequirement(result, manifestResults, entities), proofByRequirement.get(result.entity.id));
194
222
  return diagnostic === undefined ? [] : [diagnostic];
@@ -311,11 +311,11 @@ export async function executeStatus(_input, context) {
311
311
  // Source hashes can all match while the compilation itself is lossy, so
312
312
  // a compiler contract change alone makes the store stale.
313
313
  ...(compilerReason ? { syncState: "stale", dirty: true } : {}),
314
- // An empty store has no stale file to report, yet nothing in it is
315
- // current: the authored sources were never compiled.
316
- ...(storeUncompiled && store.state === "healthy"
317
- ? { syncState: "stale", dirty: true }
318
- : {}),
314
+ // A missing or empty store has no stale file to report, yet nothing in
315
+ // it is current: the authored sources were never compiled. (A missing
316
+ // store without authored sources, and an unreadable or incomplete one,
317
+ // stay "unknown".)
318
+ ...(storeUncompiled ? { syncState: "stale", dirty: true } : {}),
319
319
  branchAttachment: attachment,
320
320
  branchStore: store,
321
321
  ...(engineStatus ? { engineStatus } : {}),
@@ -1 +1 @@
1
- {"version":3,"file":"result-envelope.d.ts","sourceRoot":"","sources":["../../../src/public/operations/result-envelope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE7E,eAAO,MAAM,qBAAqB,EAAG,CAAU,CAAC;AAoChD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,UAAU,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAS/B;AAoFD,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC,GAAG,MAAM,CAET;AAED,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE;IACJ,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC,EACD,IAAI,EAAE,CAAC,EACP,OAAO,GAAE,OAAO,CACd,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,QAAQ,GAAG,aAAa,GAAG,aAAa,GAAG,OAAO,CAAC,CACxE,GAAG;IACF;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CACzB,GACL,UAAU,CAAC,CAAC,CAAC,CAkCf;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKrD;AAGD,MAAM,WAAW,uBAAuB;IACtC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,GACd,uBAAuB,CAYzB;AAaD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC3C,IAAI,CAKN"}
1
+ {"version":3,"file":"result-envelope.d.ts","sourceRoot":"","sources":["../../../src/public/operations/result-envelope.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE7E,eAAO,MAAM,qBAAqB,EAAG,CAAU,CAAC;AAoChD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,UAAU,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAS/B;AAoFD,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC,GAAG,MAAM,CAET;AAYD,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE;IACJ,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC,EACD,IAAI,EAAE,CAAC,EACP,OAAO,GAAE,OAAO,CACd,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,QAAQ,GAAG,aAAa,GAAG,aAAa,GAAG,OAAO,CAAC,CACxE,GAAG;IACF;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CACzB,GACL,UAAU,CAAC,CAAC,CAAC,CAmDf;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKrD;AAGD,MAAM,WAAW,uBAAuB;IACtC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,GACd,uBAAuB,CAYzB;AAaD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC3C,IAAI,CAKN"}
@@ -1,3 +1,4 @@
1
+ import { MIGRATION_PLAN_REFUSED } from "../../operations/planning/migration-refusal.js";
1
2
  export const KIBI_PROTOCOL_VERSION = 1;
2
3
  function record(value) {
3
4
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -103,14 +104,25 @@ function extraFailedEffects(declared, data) {
103
104
  export function resultVersion(spec) {
104
105
  return spec.resultVersion ?? `kibi.${spec.name}.v1`;
105
106
  }
107
+ function refusalDetail(row) {
108
+ const results = Array.isArray(row.actionResults) ? row.actionResults : [];
109
+ const failure = results.find((entry) => record(entry) && entry.outcome === "failed");
110
+ return record(failure) && typeof failure.detail === "string"
111
+ ? failure.detail
112
+ : "";
113
+ }
106
114
  export function toKibiResult(spec, data, options = {}) {
107
115
  const row = record(data) ? data : undefined;
116
+ // A migration plan whose actions were all refused changed nothing: it is
117
+ // an error, not a success that needs reconciliation.
118
+ const migrationRefused = row?.version === "kibi.migration-apply-result.v1" &&
119
+ row.outcome === "refused";
108
120
  let status = options.status;
109
121
  if (!status) {
110
122
  status = "success";
111
123
  if (row?.status === "committed_with_repairs")
112
124
  status = "committed_with_repairs";
113
- if (row?.status === "rejected")
125
+ if (row?.status === "rejected" || migrationRefused)
114
126
  status = "error";
115
127
  }
116
128
  const terminalError = row?.status === "rejected"
@@ -119,9 +131,17 @@ export function toKibiResult(spec, data, options = {}) {
119
131
  message: "Bootstrap stopped at a deterministic failure. Inspect actionResults for committed actions and re-plan from the current state.",
120
132
  retryable: false,
121
133
  }
122
- : undefined;
134
+ : migrationRefused
135
+ ? {
136
+ code: MIGRATION_PLAN_REFUSED,
137
+ message: `Migration plan refused; nothing was changed. ${refusalDetail(row)}`.trim(),
138
+ retryable: false,
139
+ }
140
+ : undefined;
123
141
  const resultError = options.error ?? terminalError;
124
- const outcome = effectOutcome(status, options);
142
+ const outcome = effectOutcome(status, migrationRefused && options.attempted === undefined
143
+ ? { ...options, attempted: false }
144
+ : options);
125
145
  return {
126
146
  kibiProtocol: KIBI_PROTOCOL_VERSION,
127
147
  operation: spec.name,
@@ -2,7 +2,7 @@
2
2
  id: kibi-usage
3
3
  name: Kibi Usage
4
4
  description: Use Kibi's source-first, exact-Git, migration-aware, proof-aware operations safely across MCP or the trusted local CLI, including partial completion repair.
5
- version: 2.12.0
5
+ version: 2.13.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -148,7 +148,13 @@ error `BOOTSTRAP_PLAN_REJECTED` can follow earlier committed actions: inspect
148
148
  `data.actionResults` and `data.applied`, then re-plan from the current state.
149
149
  Its journal is terminal; never recover or replay it. `BOOTSTRAP_PLAN_INVALID`
150
150
  fails preflight before bootstrap writes or a new journal; obtain a corrected
151
- plan. If the status is
151
+ plan. A migration plan whose actions were refused before changing anything
152
+ returns `status: error`, `MIGRATION_PLAN_REFUSED` and `data.outcome:
153
+ refused`: nothing changed, so read the action `detail` and get a new plan.
154
+ `data.outcome: reconciliation_required` (status success) means an action
155
+ failed mid-way: inspect its effect before retrying. `kb_delete` of an
156
+ authored entity returns status success with `deleted: 0`; its `errors` say
157
+ why nothing was deleted. If the status is
152
158
  `committed_with_repairs`, the mutation already committed: inspect failed effects,
153
159
  execute each required repair action in order, and never retry the original
154
160
  operation. Record effect failures, followed actions, and unsafe retries in
@@ -61,16 +61,16 @@ A schema can close an argument with `argument_constants` (allowed values keyed b
61
61
 
62
62
  ### Deny predicate
63
63
 
64
- Claim: “Suspended users must not publish articles.” When `permission_rule` fits, preserve its positive schema name and encode prohibition as polarity:
64
+ Claim: “Suspended users must not publish articles.” When `permission_rule` fits, preserve its positive schema name and encode prohibition as polarity. `kb_model` (`mode: "predicates"`) plans this fact for the claim; the built-in schema's fourth argument, `decision`, repeats the polarity:
65
65
 
66
66
  ```yaml
67
67
  id: FACT-SUSPENDED-PUBLISH-DENIED
68
68
  status: active
69
69
  fact_kind: predicate
70
70
  predicate_name: permission_rule
71
- predicate_args: [suspended_user, publish, article]
71
+ predicate_args: [suspended_user, publish, articles, deny]
72
72
  polarity: deny
73
- canonical_key: permission_rule(suspended_user,publish,article)
73
+ canonical_key: permission_rule(suspended_user,publish,articles,deny)
74
74
  claim_key: <advisor-issued key for this exact clause>
75
75
  claim_text: Suspended users must not publish articles.
76
76
  relationship: { type: requires_predicate, from: REQ-SUSPENDED-PUBLISH-DENIED, to: FACT-SUSPENDED-PUBLISH-DENIED }
@@ -51,6 +51,15 @@ proof_bindings: # optional provenance metadata
51
51
 
52
52
  Provenance lives in `proof_bindings`; the contract stays semantic.
53
53
 
54
+ - Every `required_proofs[].symbol_id` must name an existing `symbol` entity,
55
+ or `kb_check` reports `proof-contract-symbols`. Create the symbols first
56
+ (`kb_upsert`, narrow method-level granularity, never a `type-shape`
57
+ symbol): a production symbol `implements` the requirement and is
58
+ `covered_by` the test; an executable test symbol is `executable_for` it.
59
+ - `proof_bindings[].source_file`, when set, must equal the named symbol's
60
+ `sourceFile`: for a production symbol that is the production file, not the
61
+ spec file that exercises it. Omit it when unsure.
62
+
54
63
  ## Integration configuration
55
64
 
56
65
  `.kb/proof/integrations.json` (`kibi.proof-integration.v1`) is tracked,
@@ -62,12 +71,21 @@ returns `integrationPlan`, a hash-bound `kibi.migration-plan.v2`, plus the
62
71
  proposal to the human, then apply the plan with `kb_apply_plan` (or
63
72
  `kibi apply-plan --input -`) passing `plan`, `approvedPlanHash` (the plan's
64
73
  `planHash`) and `approvedActionIds`. Once the file exists, a create plan is
65
- refused; `kibi proof inspect --update <id> --json` plans adding or replacing
66
- one named integration.
74
+ refused (`status: error`, `MIGRATION_PLAN_REFUSED`, `data.outcome: refused`;
75
+ nothing is written); `kibi proof inspect --update <id> --json` plans adding or
76
+ replacing one named integration.
67
77
 
68
78
  - `producer: command` — Kibi synthesizes the envelope from the process
69
79
  outcome; obligations are bound with `aggregate_run` provenance and the
70
- single process invocation counts as the known first attempt.
80
+ single process invocation counts as the known first attempt. The run is
81
+ judged as a whole: any failing test in it fails every obligation of every
82
+ test that names the integration, so a suite with unrelated failures proves
83
+ nothing. Narrow the command to the proof-bearing tests with
84
+ `kibi proof inspect --update <id> --command "<command>" --json` (without
85
+ `--command` the proposal is the `package.json` `test` script or the
86
+ detected runner again), or make the run attributable per test: have the
87
+ command write a `kibi.proof-test-report.v1` to the path in
88
+ `KIBI_PROOF_TEST_REPORT` so each test is judged by its own steps.
71
89
  - `producer: playwright` (or custom) — the child emits `kibi.proof-run.v1`
72
90
  itself (bundled reporter: `kibi-cli/playwright-reporter`).
73
91
  - `producer: junit` / `tap` — Kibi converts the native report at `artifact`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-cli",
3
- "version": "2.16.0",
3
+ "version": "2.17.0",
4
4
  "type": "module",
5
5
  "description": "Kibi CLI for knowledge base management",
6
6
  "engines": {
@@ -235,7 +235,7 @@
235
235
  "fast-glob": "^3.2.12",
236
236
  "gray-matter": "^4.0.3",
237
237
  "js-yaml": "^4.1.0",
238
- "kibi-core": "^0.18.1",
238
+ "kibi-core": "^0.18.2",
239
239
  "kibi-plugin-builtin": "^0.5.0",
240
240
  "kibi-plugin-sdk": "^0.5.0",
241
241
  "kibi-swipl": "^1.0.1",
@@ -276,6 +276,46 @@ function coverageForRequirement(
276
276
  };
277
277
  }
278
278
 
279
+ /**
280
+ * Requirement statuses that keep a requirement in the coverage-depth review.
281
+ * `closed` is reviewed only when tagged `implemented` (the documented
282
+ * "done, with evidence" shape); a plain closed, deprecated or superseded
283
+ * requirement is retired history, not a coverage gap.
284
+ */
285
+ const REVIEWED_REQUIREMENT_STATUSES = new Set([
286
+ "open",
287
+ "in_progress",
288
+ "active",
289
+ "approved",
290
+ ]);
291
+
292
+ function supersededRequirementIds(
293
+ manifestResults: readonly ExtractionResult[],
294
+ ): ReadonlySet<string> {
295
+ return new Set(
296
+ manifestResults.flatMap((result) =>
297
+ result.entity.type === "req"
298
+ ? result.relationships.flatMap((relationship) =>
299
+ relationship.type === "supersedes" ? [relationship.to] : [],
300
+ )
301
+ : [],
302
+ ),
303
+ );
304
+ }
305
+
306
+ // implements REQ-audit-quality-diagnostics-v1
307
+ function isReviewableRequirement(
308
+ requirement: ExtractedEntity,
309
+ superseded: ReadonlySet<string>,
310
+ ): boolean {
311
+ if (superseded.has(requirement.id)) return false;
312
+ const status = requirement.status.trim().toLowerCase();
313
+ if (REVIEWED_REQUIREMENT_STATUSES.has(status)) return true;
314
+ return (
315
+ status === "closed" && (requirement.tags ?? []).includes("implemented")
316
+ );
317
+ }
318
+
279
319
  export function createCoverageDepthQualityDiagnostics(
280
320
  manifestResults: readonly ExtractionResult[],
281
321
  proofByRequirement: ReadonlyMap<
@@ -289,8 +329,13 @@ export function createCoverageDepthQualityDiagnostics(
289
329
  > = new Map(),
290
330
  ): readonly QualityDiagnostic[] {
291
331
  const entities = entitiesById(manifestResults);
332
+ const superseded = supersededRequirementIds(manifestResults);
292
333
  return manifestResults
293
- .filter((result) => result.entity.type === "req")
334
+ .filter(
335
+ (result) =>
336
+ result.entity.type === "req" &&
337
+ isReviewableRequirement(result.entity, superseded),
338
+ )
294
339
  .flatMap((result) => {
295
340
  const diagnostic = createDiagnostic(
296
341
  coverageForRequirement(result, manifestResults, entities),
@@ -507,11 +507,11 @@ export async function executeStatus(
507
507
  // Source hashes can all match while the compilation itself is lossy, so
508
508
  // a compiler contract change alone makes the store stale.
509
509
  ...(compilerReason ? { syncState: "stale", dirty: true } : {}),
510
- // An empty store has no stale file to report, yet nothing in it is
511
- // current: the authored sources were never compiled.
512
- ...(storeUncompiled && store.state === "healthy"
513
- ? { syncState: "stale", dirty: true }
514
- : {}),
510
+ // A missing or empty store has no stale file to report, yet nothing in
511
+ // it is current: the authored sources were never compiled. (A missing
512
+ // store without authored sources, and an unreadable or incomplete one,
513
+ // stay "unknown".)
514
+ ...(storeUncompiled ? { syncState: "stale", dirty: true } : {}),
515
515
  branchAttachment: attachment,
516
516
  branchStore: store,
517
517
  ...(engineStatus ? { engineStatus } : {}),
@@ -1,3 +1,4 @@
1
+ import { MIGRATION_PLAN_REFUSED } from "../../operations/planning/migration-refusal.js";
1
2
  import type { KibiResult, OperationEffect, OperationName } from "./types.js";
2
3
 
3
4
  export const KIBI_PROTOCOL_VERSION = 1 as const;
@@ -139,6 +140,16 @@ export function resultVersion(spec: {
139
140
  return spec.resultVersion ?? `kibi.${spec.name}.v1`;
140
141
  }
141
142
 
143
+ function refusalDetail(row: Record<string, unknown>): string {
144
+ const results = Array.isArray(row.actionResults) ? row.actionResults : [];
145
+ const failure = results.find(
146
+ (entry) => record(entry) && entry.outcome === "failed",
147
+ );
148
+ return record(failure) && typeof failure.detail === "string"
149
+ ? failure.detail
150
+ : "";
151
+ }
152
+
142
153
  export function toKibiResult<T>(
143
154
  spec: {
144
155
  readonly name: OperationName | string;
@@ -158,12 +169,17 @@ export function toKibiResult<T>(
158
169
  } = {},
159
170
  ): KibiResult<T> {
160
171
  const row = record(data) ? data : undefined;
172
+ // A migration plan whose actions were all refused changed nothing: it is
173
+ // an error, not a success that needs reconciliation.
174
+ const migrationRefused =
175
+ row?.version === "kibi.migration-apply-result.v1" &&
176
+ row.outcome === "refused";
161
177
  let status = options.status;
162
178
  if (!status) {
163
179
  status = "success";
164
180
  if (row?.status === "committed_with_repairs")
165
181
  status = "committed_with_repairs";
166
- if (row?.status === "rejected") status = "error";
182
+ if (row?.status === "rejected" || migrationRefused) status = "error";
167
183
  }
168
184
  const terminalError =
169
185
  row?.status === "rejected"
@@ -173,9 +189,21 @@ export function toKibiResult<T>(
173
189
  "Bootstrap stopped at a deterministic failure. Inspect actionResults for committed actions and re-plan from the current state.",
174
190
  retryable: false,
175
191
  }
176
- : undefined;
192
+ : migrationRefused
193
+ ? {
194
+ code: MIGRATION_PLAN_REFUSED,
195
+ message:
196
+ `Migration plan refused; nothing was changed. ${refusalDetail(row)}`.trim(),
197
+ retryable: false,
198
+ }
199
+ : undefined;
177
200
  const resultError = options.error ?? terminalError;
178
- const outcome = effectOutcome(status, options);
201
+ const outcome = effectOutcome(
202
+ status,
203
+ migrationRefused && options.attempted === undefined
204
+ ? { ...options, attempted: false }
205
+ : options,
206
+ );
179
207
  return {
180
208
  kibiProtocol: KIBI_PROTOCOL_VERSION,
181
209
  operation: spec.name as OperationName,
@@ -2,7 +2,7 @@
2
2
  id: kibi-usage
3
3
  name: Kibi Usage
4
4
  description: Use Kibi's source-first, exact-Git, migration-aware, proof-aware operations safely across MCP or the trusted local CLI, including partial completion repair.
5
- version: 2.12.0
5
+ version: 2.13.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -148,7 +148,13 @@ error `BOOTSTRAP_PLAN_REJECTED` can follow earlier committed actions: inspect
148
148
  `data.actionResults` and `data.applied`, then re-plan from the current state.
149
149
  Its journal is terminal; never recover or replay it. `BOOTSTRAP_PLAN_INVALID`
150
150
  fails preflight before bootstrap writes or a new journal; obtain a corrected
151
- plan. If the status is
151
+ plan. A migration plan whose actions were refused before changing anything
152
+ returns `status: error`, `MIGRATION_PLAN_REFUSED` and `data.outcome:
153
+ refused`: nothing changed, so read the action `detail` and get a new plan.
154
+ `data.outcome: reconciliation_required` (status success) means an action
155
+ failed mid-way: inspect its effect before retrying. `kb_delete` of an
156
+ authored entity returns status success with `deleted: 0`; its `errors` say
157
+ why nothing was deleted. If the status is
152
158
  `committed_with_repairs`, the mutation already committed: inspect failed effects,
153
159
  execute each required repair action in order, and never retry the original
154
160
  operation. Record effect failures, followed actions, and unsafe retries in
@@ -61,16 +61,16 @@ A schema can close an argument with `argument_constants` (allowed values keyed b
61
61
 
62
62
  ### Deny predicate
63
63
 
64
- Claim: “Suspended users must not publish articles.” When `permission_rule` fits, preserve its positive schema name and encode prohibition as polarity:
64
+ Claim: “Suspended users must not publish articles.” When `permission_rule` fits, preserve its positive schema name and encode prohibition as polarity. `kb_model` (`mode: "predicates"`) plans this fact for the claim; the built-in schema's fourth argument, `decision`, repeats the polarity:
65
65
 
66
66
  ```yaml
67
67
  id: FACT-SUSPENDED-PUBLISH-DENIED
68
68
  status: active
69
69
  fact_kind: predicate
70
70
  predicate_name: permission_rule
71
- predicate_args: [suspended_user, publish, article]
71
+ predicate_args: [suspended_user, publish, articles, deny]
72
72
  polarity: deny
73
- canonical_key: permission_rule(suspended_user,publish,article)
73
+ canonical_key: permission_rule(suspended_user,publish,articles,deny)
74
74
  claim_key: <advisor-issued key for this exact clause>
75
75
  claim_text: Suspended users must not publish articles.
76
76
  relationship: { type: requires_predicate, from: REQ-SUSPENDED-PUBLISH-DENIED, to: FACT-SUSPENDED-PUBLISH-DENIED }
@@ -51,6 +51,15 @@ proof_bindings: # optional provenance metadata
51
51
 
52
52
  Provenance lives in `proof_bindings`; the contract stays semantic.
53
53
 
54
+ - Every `required_proofs[].symbol_id` must name an existing `symbol` entity,
55
+ or `kb_check` reports `proof-contract-symbols`. Create the symbols first
56
+ (`kb_upsert`, narrow method-level granularity, never a `type-shape`
57
+ symbol): a production symbol `implements` the requirement and is
58
+ `covered_by` the test; an executable test symbol is `executable_for` it.
59
+ - `proof_bindings[].source_file`, when set, must equal the named symbol's
60
+ `sourceFile`: for a production symbol that is the production file, not the
61
+ spec file that exercises it. Omit it when unsure.
62
+
54
63
  ## Integration configuration
55
64
 
56
65
  `.kb/proof/integrations.json` (`kibi.proof-integration.v1`) is tracked,
@@ -62,12 +71,21 @@ returns `integrationPlan`, a hash-bound `kibi.migration-plan.v2`, plus the
62
71
  proposal to the human, then apply the plan with `kb_apply_plan` (or
63
72
  `kibi apply-plan --input -`) passing `plan`, `approvedPlanHash` (the plan's
64
73
  `planHash`) and `approvedActionIds`. Once the file exists, a create plan is
65
- refused; `kibi proof inspect --update <id> --json` plans adding or replacing
66
- one named integration.
74
+ refused (`status: error`, `MIGRATION_PLAN_REFUSED`, `data.outcome: refused`;
75
+ nothing is written); `kibi proof inspect --update <id> --json` plans adding or
76
+ replacing one named integration.
67
77
 
68
78
  - `producer: command` — Kibi synthesizes the envelope from the process
69
79
  outcome; obligations are bound with `aggregate_run` provenance and the
70
- single process invocation counts as the known first attempt.
80
+ single process invocation counts as the known first attempt. The run is
81
+ judged as a whole: any failing test in it fails every obligation of every
82
+ test that names the integration, so a suite with unrelated failures proves
83
+ nothing. Narrow the command to the proof-bearing tests with
84
+ `kibi proof inspect --update <id> --command "<command>" --json` (without
85
+ `--command` the proposal is the `package.json` `test` script or the
86
+ detected runner again), or make the run attributable per test: have the
87
+ command write a `kibi.proof-test-report.v1` to the path in
88
+ `KIBI_PROOF_TEST_REPORT` so each test is judged by its own steps.
71
89
  - `producer: playwright` (or custom) — the child emits `kibi.proof-run.v1`
72
90
  itself (bundled reporter: `kibi-cli/playwright-reporter`).
73
91
  - `producer: junit` / `tap` — Kibi converts the native report at `artifact`