@saasontools/strauss-kb 0.1.5 → 0.1.6

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/dist/cli-main.cjs CHANGED
@@ -45,6 +45,11 @@ var kbActorStampSchema = import_zod.z.object({
45
45
  by: import_zod.z.string().min(1),
46
46
  at: import_zod.z.string().min(1)
47
47
  }).passthrough();
48
+ var kbVerifiedEventSchema = kbActorStampSchema.extend({
49
+ note: import_zod.z.string().refine((s) => s.trim().length > 0, {
50
+ message: "note must say what the check found"
51
+ })
52
+ });
48
53
  var kbAnchorSchema = import_zod.z.object({
49
54
  file: import_zod.z.string().min(1),
50
55
  symbol: import_zod.z.string().min(1).optional()
@@ -1576,8 +1581,37 @@ var validateCommand = define({
1576
1581
  failsWhen: (result) => Array.isArray(result) && result.length > 0
1577
1582
  });
1578
1583
 
1579
- // src/commands/write.ts
1584
+ // src/commands/verify.ts
1580
1585
  var import_zod26 = require("zod");
1586
+ var verifyCommand = define({
1587
+ name: "verify",
1588
+ tool: "kb_verify",
1589
+ usage: "verify <concept-id> --note <text>",
1590
+ description: "Append one verified[] event \u2014 who checked the record, when, and what the check found. Appends only; prior events are never rewritten. A record's own generator is refused unless the actor is human: re-reading your own output is not an independent check.",
1591
+ input: import_zod26.z.object({
1592
+ bundlePath,
1593
+ conceptId,
1594
+ note: import_zod26.z.string().refine((s) => s.trim().length > 0, {
1595
+ message: "note must say what the check found"
1596
+ })
1597
+ }),
1598
+ fromArgv: (argv, path) => ({
1599
+ bundlePath: path,
1600
+ conceptId: argv[1],
1601
+ note: argvFlag(argv, "--note")
1602
+ }),
1603
+ run: async ({ store, actor, now }, { bundlePath: path, conceptId: id, note }) => {
1604
+ await assertBaseNotFrozen(process.cwd(), path);
1605
+ const record = await store.verify(path, id, note, actor, now());
1606
+ return {
1607
+ conceptId: record.conceptId,
1608
+ verified: record.frontmatter.verified?.length ?? 0
1609
+ };
1610
+ }
1611
+ });
1612
+
1613
+ // src/commands/write.ts
1614
+ var import_zod27 = require("zod");
1581
1615
  var writeCommand = define({
1582
1616
  name: "write",
1583
1617
  tool: "kb_write",
@@ -1591,9 +1625,9 @@ var writeCommand = define({
1591
1625
  "- Prefer a new record over overloading an existing one, and keep each short. A record nobody finishes reading is not durable memory.",
1592
1626
  "- Records are never deleted; supersede instead, so the earlier reasoning stays inspectable."
1593
1627
  ].join("\n"),
1594
- input: import_zod26.z.object({
1628
+ input: import_zod27.z.object({
1595
1629
  bundlePath,
1596
- type: import_zod26.z.enum(KB_RECORD_TYPES),
1630
+ type: import_zod27.z.enum(KB_RECORD_TYPES),
1597
1631
  input: composeInputSchema
1598
1632
  }),
1599
1633
  fromArgv: async (argv, path, stdin) => ({
@@ -1617,7 +1651,7 @@ var writeCommand = define({
1617
1651
  });
1618
1652
 
1619
1653
  // src/commands/write-decision.ts
1620
- var import_zod27 = require("zod");
1654
+ var import_zod28 = require("zod");
1621
1655
  var writeDecisionCommand = define({
1622
1656
  name: "write-decision",
1623
1657
  tool: "kb_write_decision",
@@ -1630,7 +1664,7 @@ var writeDecisionCommand = define({
1630
1664
  "- `alternative` is what you turned down and why, not a list of everything considered.",
1631
1665
  "- A reference to material you read goes in `sources`; a reference to code goes in `anchors`; a reference to another record goes in `relatedConceptIds`."
1632
1666
  ].join("\n"),
1633
- input: import_zod27.z.object({ bundlePath, input: decisionInputSchema }),
1667
+ input: import_zod28.z.object({ bundlePath, input: decisionInputSchema }),
1634
1668
  fromArgv: async (_argv, path, stdin) => ({
1635
1669
  bundlePath: path,
1636
1670
  input: JSON.parse(await stdin())
@@ -1658,6 +1692,7 @@ var KB_COMMANDS = [
1658
1692
  statusCommand,
1659
1693
  supersedeCommand,
1660
1694
  answerCommand,
1695
+ verifyCommand,
1661
1696
  loadCommand,
1662
1697
  queryCommand,
1663
1698
  traceCommand,
@@ -1773,6 +1808,25 @@ var KbWriteConflictError = class extends BaseError {
1773
1808
  }
1774
1809
  conceptId;
1775
1810
  };
1811
+ var KbSelfVerificationError = class extends BaseError {
1812
+ constructor(conceptId2, actor, generatedBy) {
1813
+ super({
1814
+ message: `kb: ${conceptId2} was generated by ${generatedBy}, and a record's generator cannot verify it \u2014 only a human or a different actor can`,
1815
+ errorType: "KbSelfVerification" /* KbSelfVerification */,
1816
+ code: 400,
1817
+ fault: "User" /* User */,
1818
+ retriable: false,
1819
+ reportToUser: true,
1820
+ details: { conceptId: conceptId2, actor, generatedBy, action: "refused" }
1821
+ });
1822
+ this.conceptId = conceptId2;
1823
+ this.actor = actor;
1824
+ this.generatedBy = generatedBy;
1825
+ }
1826
+ conceptId;
1827
+ actor;
1828
+ generatedBy;
1829
+ };
1776
1830
  var KbInvalidConceptIdError = class extends BaseError {
1777
1831
  constructor(message, details) {
1778
1832
  super({
@@ -1991,6 +2045,40 @@ var KbStore = class {
1991
2045
  { operation: `status:${status}`, by: actor }
1992
2046
  );
1993
2047
  }
2048
+ /**
2049
+ * Appends one `verified[]` event: who checked the record, when, and what the
2050
+ * check found. Append-only — prior events are history, and are spread into
2051
+ * the new array untouched rather than reshaped through the write schema.
2052
+ *
2053
+ * A record's generator cannot verify its own record unless the actor is
2054
+ * human: the generator re-reading its own output is not an independent
2055
+ * check. The rule runs before the mutation so a refusal never publishes,
2056
+ * and the refusal is logged under its own operation name — `mutate` only
2057
+ * logs what it publishes.
2058
+ */
2059
+ async verify(bundlePath2, conceptId2, note, actor = "unknown", at = (/* @__PURE__ */ new Date()).toISOString()) {
2060
+ const event = kbVerifiedEventSchema.parse({ by: actor, at, note });
2061
+ const existing = await this.read(bundlePath2, conceptId2);
2062
+ if (!existing) throw new KbRecordNotFoundError(conceptId2);
2063
+ const generatedBy = existing.frontmatter.generated?.by;
2064
+ if (generatedBy !== void 0 && actor.toLowerCase() === generatedBy.toLowerCase() && !normalizeActor(actor).startsWith("human:")) {
2065
+ await this.record(this.root(bundlePath2), {
2066
+ operation: "verify:refused",
2067
+ conceptId: conceptId2,
2068
+ by: actor
2069
+ });
2070
+ throw new KbSelfVerificationError(conceptId2, actor, generatedBy);
2071
+ }
2072
+ return this.mutate(
2073
+ bundlePath2,
2074
+ conceptId2,
2075
+ (frontmatter) => ({
2076
+ ...frontmatter,
2077
+ verified: [...frontmatter.verified ?? [], event]
2078
+ }),
2079
+ { operation: "verify", by: actor }
2080
+ );
2081
+ }
1994
2082
  /**
1995
2083
  * Marks `conceptId` superseded by `replacementId`, and links both directions.
1996
2084
  *
@@ -2325,6 +2413,11 @@ function matches(record, needle) {
2325
2413
  (field) => field?.toLowerCase().includes(needle)
2326
2414
  );
2327
2415
  }
2416
+ function normalizeActor(id) {
2417
+ const colon = id.indexOf(":");
2418
+ if (colon === -1) return id.toLowerCase();
2419
+ return id.slice(0, colon + 1).toLowerCase() + id.slice(colon + 1);
2420
+ }
2328
2421
  function digest(contents) {
2329
2422
  return (0, import_node_crypto.createHash)("sha256").update(contents).digest("hex");
2330
2423
  }