@hobin/developer 0.1.10 → 0.1.11

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 CHANGED
@@ -84,7 +84,10 @@ details but are actually product, model, boundary, or evidence questions.
84
84
  | Branches lose rationale | Evidence replays with its branch |
85
85
 
86
86
  Developer coordinates judgment; Pi still reads, edits, runs, and tests the
87
- product with its normal tools.
87
+ product with its normal tools. Across those judgments, an unchecked assertion,
88
+ cast, non-null claim, or typed decode is never treated as evidence that a domain
89
+ invariant holds: broader input must cross an owned parser or smart-constructor
90
+ boundary whose success returns the refined value.
88
91
 
89
92
  ## How it works
90
93
 
@@ -118,6 +121,7 @@ and the plausible judgment routes add no useful information.
118
121
  | Clarify a feature | Separate meaning, constraints, examples, and blockers |
119
122
  | Model behavior | Enumerate cases, contracts, transitions, and guarantees |
120
123
  | Shape code | Expose data, interfaces, collaboration, state, and checks |
124
+ | Refine input safely | Turn broader input into invariant-carrying domain values before dependent effects |
121
125
  | Inspect a design | Identify structural movement and model-code mismatch |
122
126
  | Review an abstraction | Keep, revise, split, reject, or defer the candidate |
123
127
  | Time a refactor | Separate behavior work and judge the cost of delay |
@@ -200,7 +200,7 @@ separation behavior, and pass-but-wrong cases.
200
200
  | --- | --- | --- |
201
201
  | `abstraction-review` | `candidate-contract-review`, `failed-candidate-check`, `candidate-calibration` | review, failure localization, or promise-based calibration; no shadow construction |
202
202
  | `model` | `condition-space`, `contract-replacement`, `relational-constraints`, `temporal-behavior`, `proof-obligation`, `solver-result-boundary`, `logic-query-semantics`, `planning-model` | one model artifact per uncertainty and stop condition |
203
- | `sketch` | `data-driven-design`, `data-shape-template`, `composition-by-wishes`, `earned-abstraction`, `generative-recursion`, `accumulator-invariant`, `design-levels`, `representation-barrier`, `closure-interface`, `process-resources`, `state-history-order`, `generic-dispatch`, `meaning-preserving-conversion`, `language-semantics`, `runtime-compilation`, `responsibility-collaboration`, `variation-role`, `type-transition`, `selection-creation` | one implementable design question per route, with route-local common kernels only where required |
203
+ | `sketch` | `data-driven-design`, `data-shape-template`, `composition-by-wishes`, `earned-abstraction`, `generative-recursion`, `accumulator-invariant`, `evidence-preserving-boundary`, `design-levels`, `representation-barrier`, `closure-interface`, `process-resources`, `state-history-order`, `generic-dispatch`, `meaning-preserving-conversion`, `language-semantics`, `runtime-compilation`, `responsibility-collaboration`, `variation-role`, `type-transition`, `selection-creation` | one implementable design question per route, with route-local common kernels only where required |
204
204
  | `signal` | `behavior-preserving-movement` | singleton |
205
205
  | `schedule` | `structural-timing-tradeoff` | singleton |
206
206
  | `naming-judgment` | `domain-sense-boundary` | singleton |
@@ -53,6 +53,7 @@ import {
53
53
  applyDeveloperEvent,
54
54
  canApplyDeveloperEvent,
55
55
  developerSnapshot,
56
+ formatInvariantHandling,
56
57
  initialState,
57
58
  normalizeDeveloperEvent,
58
59
  parseChoiceResponseSpec,
@@ -62,6 +63,7 @@ import {
62
63
  type ChoiceResponseSpec,
63
64
  type DeveloperState,
64
65
  type ImplementationProfile,
66
+ type InvariantHandling,
65
67
  type FocusEvent,
66
68
  type JudgmentEvent,
67
69
  type PendingQuestion,
@@ -215,6 +217,59 @@ interface ChoiceResponseSpecInput {
215
217
  fields: ChoiceResponseFieldInput[];
216
218
  }
217
219
 
220
+ type InvariantHandlingInput =
221
+ | {
222
+ kind: "not-applicable";
223
+ reason: string;
224
+ }
225
+ | {
226
+ kind: "evidence-preserving-boundary";
227
+ raw_representation: string;
228
+ refined_representation: string;
229
+ producer: string;
230
+ failure: string;
231
+ first_effect: string;
232
+ }
233
+ | {
234
+ kind: "trusted-compiler-gap";
235
+ assertion: string;
236
+ established_by: string;
237
+ limitation: string;
238
+ containment: string;
239
+ verification: string;
240
+ };
241
+
242
+ function invariantHandlingFromInput(
243
+ input: InvariantHandlingInput | undefined,
244
+ ): InvariantHandling {
245
+ if (!input) {
246
+ return {
247
+ kind: "not-applicable",
248
+ reason:
249
+ "Legacy direct tool execution omitted the now-required invariant-handling declaration.",
250
+ };
251
+ }
252
+ if (input.kind === "not-applicable") return input;
253
+ if (input.kind === "evidence-preserving-boundary") {
254
+ return {
255
+ kind: input.kind,
256
+ rawRepresentation: input.raw_representation,
257
+ refinedRepresentation: input.refined_representation,
258
+ producer: input.producer,
259
+ failure: input.failure,
260
+ firstEffect: input.first_effect,
261
+ };
262
+ }
263
+ return {
264
+ kind: input.kind,
265
+ assertion: input.assertion,
266
+ establishedBy: input.established_by,
267
+ limitation: input.limitation,
268
+ containment: input.containment,
269
+ verification: input.verification,
270
+ };
271
+ }
272
+
218
273
  interface OpenQuestionInput {
219
274
  question: string;
220
275
  context?: string;
@@ -462,7 +517,9 @@ function protocolPrompt(
462
517
  "- Adapt away from that topology when evidence makes a stage not applicable, but never jump directly from a resolved model to mutation: use sketch for feature implementation or signal for existing-code structural movement first.",
463
518
  "- Route by the requested work product, not keywords: use sketch to create an original caller-facing interface, representation boundary, ownership map, or collaboration; use abstraction-review only when a concrete candidate surface already exists to keep/revise/split/reject/defer; use model first only when unresolved cases, rules, or policy can materially change that surface.",
464
519
  `- Call ${ROUTE_TOOL} for exactly one concrete judgment or one green-to-green implementation movement.`,
465
- "- Use target=implementation only when the next local movement, stable landing, and narrow verification are already justified; otherwise choose the focused skill whose scope fits the current question.",
520
+ "- Use target=implementation only when the next local movement, stable landing, narrow verification, and invariant handling are already justified; otherwise choose the focused skill whose scope fits the current question.",
521
+ "- Invariant gate: an unchecked assertion, cast, non-null claim, ignored conversion result, or typed deserialization target is not evidence that broader, external, persisted, legacy, or otherwise less-trusted input satisfies a domain invariant. Parse or construct a refined value whose success carries the learned information before dependent effects; validation followed by narrowing does not satisfy this gate.",
522
+ "- Every implementation route must classify invariant handling as not applicable, an evidence-preserving boundary, or a trusted compiler gap. A trusted compiler gap is valid only after a complete check or trusted contract already established the fact, the language cannot retain it, the unsafe operation is isolated to one owner, and a falsifier is named. Encountering a missing boundary requires sketch, not an implementation assertion.",
466
523
  "- An implementation step has one observable difference and one structural or behavioral purpose. Stop when its failure is locally explainable and the repository is green, pausable, and reviewable.",
467
524
  "- For implementation structural work intended to preserve behavior, set execution_profile=behavior-preserving-structure; omit the field for other implementation actions and every skill route.",
468
525
  `- Follow the routed method, then close the route with ${JUDGMENT_TOOL}. After every implementation stable landing, route again from the new evidence before selecting another movement.`,
@@ -511,6 +568,11 @@ function protocolPrompt(
511
568
  lines.push(
512
569
  `Active route: ${state.activeRoute.routeId} · ${state.activeRoute.target} · ${state.activeRoute.question}`,
513
570
  );
571
+ if (state.activeRoute.implementationStep) {
572
+ lines.push(
573
+ `Active invariant handling: ${formatInvariantHandling(state.activeRoute.implementationStep.invariantHandling)}. Stop and route sketch instead of mutating if this declaration is contradicted by the code.`,
574
+ );
575
+ }
514
576
  if (state.activeRoute.methodLocation) {
515
577
  lines.push(
516
578
  `Active skill location: ${state.activeRoute.methodLocation}. Read it again if compaction or a later turn no longer contains the full instructions.`,
@@ -825,6 +887,83 @@ export default async function developer(pi: ExtensionAPI) {
825
887
  },
826
888
  { additionalProperties: false },
827
889
  );
890
+ const InvariantHandlingParam = Type.Union([
891
+ Type.Object(
892
+ {
893
+ kind: Type.Literal("not-applicable"),
894
+ reason: Type.String({
895
+ minLength: 1,
896
+ maxLength: MAX_EVIDENCE_CHARS,
897
+ description:
898
+ "Why this movement neither creates nor narrows broader or less-trusted data into an invariant-carrying value",
899
+ }),
900
+ },
901
+ { additionalProperties: false },
902
+ ),
903
+ Type.Object(
904
+ {
905
+ kind: Type.Literal("evidence-preserving-boundary"),
906
+ raw_representation: Type.String({
907
+ minLength: 1,
908
+ maxLength: MAX_EVIDENCE_CHARS,
909
+ }),
910
+ refined_representation: Type.String({
911
+ minLength: 1,
912
+ maxLength: MAX_EVIDENCE_CHARS,
913
+ }),
914
+ producer: Type.String({
915
+ minLength: 1,
916
+ maxLength: MAX_EVIDENCE_CHARS,
917
+ description:
918
+ "Parser, refinement function, or smart constructor whose success returns the refined value",
919
+ }),
920
+ failure: Type.String({
921
+ minLength: 1,
922
+ maxLength: MAX_EVIDENCE_CHARS,
923
+ }),
924
+ first_effect: Type.String({
925
+ minLength: 1,
926
+ maxLength: MAX_EVIDENCE_CHARS,
927
+ description:
928
+ "First dependent domain effect allowed only after successful refinement",
929
+ }),
930
+ },
931
+ { additionalProperties: false },
932
+ ),
933
+ Type.Object(
934
+ {
935
+ kind: Type.Literal("trusted-compiler-gap"),
936
+ assertion: Type.String({
937
+ minLength: 1,
938
+ maxLength: MAX_EVIDENCE_CHARS,
939
+ }),
940
+ established_by: Type.String({
941
+ minLength: 1,
942
+ maxLength: MAX_EVIDENCE_CHARS,
943
+ description:
944
+ "Complete check or trusted runtime contract that already established the asserted fact",
945
+ }),
946
+ limitation: Type.String({
947
+ minLength: 1,
948
+ maxLength: MAX_EVIDENCE_CHARS,
949
+ description:
950
+ "Why ordinary language narrowing cannot retain the established fact",
951
+ }),
952
+ containment: Type.String({
953
+ minLength: 1,
954
+ maxLength: MAX_EVIDENCE_CHARS,
955
+ description: "Single parser or constructor owner containing the gap",
956
+ }),
957
+ verification: Type.String({
958
+ minLength: 1,
959
+ maxLength: MAX_EVIDENCE_CHARS,
960
+ description:
961
+ "Falsifier for the claimed prior evidence and containment",
962
+ }),
963
+ },
964
+ { additionalProperties: false },
965
+ ),
966
+ ]);
828
967
  const RouteParams = Type.Union([
829
968
  Type.Object(
830
969
  {
@@ -867,6 +1006,7 @@ export default async function developer(pi: ExtensionAPI) {
867
1006
  description:
868
1007
  "The narrowest check that can catch the likely break in this movement",
869
1008
  }),
1009
+ invariant_handling: InvariantHandlingParam,
870
1010
  alternatives_considered: Type.Optional(
871
1011
  Type.Array(RouteAlternativeParam, {
872
1012
  maxItems: 6,
@@ -892,11 +1032,11 @@ export default async function developer(pi: ExtensionAPI) {
892
1032
  name: ROUTE_TOOL,
893
1033
  label: "Developer Route Question",
894
1034
  description:
895
- "Route one concrete judgment or one green-to-green implementation movement. Route by work product: sketch creates an original design surface, abstraction-review judges an already-shaped candidate, and model settles unresolved conditions before design. Then use implementation stable landings and verify before completion.",
1035
+ "Route one concrete judgment or one green-to-green implementation movement. Route by work product: sketch creates an original design surface, abstraction-review judges an already-shaped candidate, and model settles unresolved conditions before design. Implementation must declare how invariant-bearing values are constructed without unchecked narrowing. Then use stable landings and verify before completion.",
896
1036
  promptSnippet: "Choose how to handle one development question",
897
1037
  promptGuidelines: [
898
1038
  `Call ${ROUTE_TOOL} only when there is no active Developer route.`,
899
- `Use ${ROUTE_TOOL} with the most focused skill supported by current evidence; choose sketch for an original interface or boundary, abstraction-review only for an already-shaped candidate, and model only for unresolved condition space. target=implementation requires one movement, one stable landing, and one narrow verification.`,
1039
+ `Use ${ROUTE_TOOL} with the most focused skill supported by current evidence; choose sketch for an original interface or boundary, abstraction-review only for an already-shaped candidate, and model only for unresolved condition space. target=implementation requires one movement, one stable landing, one narrow verification, and a truthful invariant_handling declaration. Validation followed by an assertion is not an evidence-preserving boundary.`,
900
1040
  `When ${ROUTE_TOOL} follows an implementation judgment with another implementation route, cite the previous landing in known_evidence and record plausible skill routes in alternatives_considered instead of selecting implementation by momentum.`,
901
1041
  `After a resolved model, use sketch for first feature implementation framing or signal for existing-code structural movement before implementation mutation.`,
902
1042
  ],
@@ -1041,6 +1181,10 @@ export default async function developer(pi: ExtensionAPI) {
1041
1181
  "# Implementation action",
1042
1182
  "",
1043
1183
  "The next local action is already justified. Keep this route open while using Pi implementation tools and collecting evidence.",
1184
+ "",
1185
+ "## Invariant gate",
1186
+ "",
1187
+ "Follow the route's invariant-handling declaration. Broader or less-trusted input must become an invariant-carrying value through a parser, refinement function, or smart constructor before dependent effects. Validation followed by an assertion, cast, non-null claim, ignored conversion result, or typed decode is not evidence. If the declared handling is incomplete, stop without mutation and route sketch. Keep any trusted compiler gap isolated to its declared owner and verify its prior evidence and containment.",
1044
1188
  ].join("\n");
1045
1189
  }
1046
1190
 
@@ -1059,6 +1203,11 @@ export default async function developer(pi: ExtensionAPI) {
1059
1203
  ? params.verification
1060
1204
  : "Run the narrowest relevant check and inspect the resulting diff or output."
1061
1205
  ).trim(),
1206
+ invariantHandling: invariantHandlingFromInput(
1207
+ "invariant_handling" in params
1208
+ ? params.invariant_handling
1209
+ : undefined,
1210
+ ),
1062
1211
  }
1063
1212
  : undefined;
1064
1213
 
@@ -1096,6 +1245,7 @@ export default async function developer(pi: ExtensionAPI) {
1096
1245
  `Movement: ${implementationStep.movement}`,
1097
1246
  `Stable landing: ${implementationStep.stopCondition}`,
1098
1247
  `Narrow verification: ${implementationStep.verification}`,
1248
+ `Invariant handling: ${formatInvariantHandling(implementationStep.invariantHandling)}`,
1099
1249
  "Stop this implementation route at that landing, record the evidence, and route again before another movement.",
1100
1250
  ]
1101
1251
  : []),
@@ -1173,7 +1323,8 @@ export default async function developer(pi: ExtensionAPI) {
1173
1323
  context.lastComponent,
1174
1324
  );
1175
1325
  }
1176
- const event = result.details as RouteEvent | undefined;
1326
+ const normalized = normalizeDeveloperEvent(result.details);
1327
+ const event = normalized?.kind === "route" ? normalized : undefined;
1177
1328
  let text = theme.fg("success", `routed ${routeRenderText(event)}`);
1178
1329
  if (expanded && event) {
1179
1330
  text += `\n${detailLine(theme, "route", event.routeId, "text")}`;
@@ -1207,6 +1358,7 @@ export default async function developer(pi: ExtensionAPI) {
1207
1358
  text += `\n${detailLine(theme, "movement", event.implementationStep.movement, "text")}`;
1208
1359
  text += `\n${detailLine(theme, "landing", event.implementationStep.stopCondition)}`;
1209
1360
  text += `\n${detailLine(theme, "verify", event.implementationStep.verification)}`;
1361
+ text += `\n${detailLine(theme, "invariant", formatInvariantHandling(event.implementationStep.invariantHandling))}`;
1210
1362
  }
1211
1363
  }
1212
1364
  if (!expanded && event)
@@ -1382,7 +1534,9 @@ export default async function developer(pi: ExtensionAPI) {
1382
1534
  context.lastComponent,
1383
1535
  );
1384
1536
  }
1385
- const event = result.details as ReferenceLoadEvent | undefined;
1537
+ const normalized = normalizeDeveloperEvent(result.details);
1538
+ const event =
1539
+ normalized?.kind === "reference-load" ? normalized : undefined;
1386
1540
  let text = theme.fg(
1387
1541
  "success",
1388
1542
  `loaded ${event?.path ?? "Developer reference"}`,
@@ -2011,7 +2165,8 @@ export default async function developer(pi: ExtensionAPI) {
2011
2165
  context.lastComponent,
2012
2166
  );
2013
2167
  }
2014
- const event = result.details as JudgmentEvent | undefined;
2168
+ const normalized = normalizeDeveloperEvent(result.details);
2169
+ const event = normalized?.kind === "judgment" ? normalized : undefined;
2015
2170
  const summary = judgmentRenderText(event);
2016
2171
  let text =
2017
2172
  event?.status === "resolved"
@@ -46,7 +46,10 @@ For movement across a boundary, update one sender or caller at a time when the
46
46
  old and new forms can safely coexist. Confirm that each intermediate state is
47
47
  valid. Do not rely on a final large diff to explain which step changed behavior.
48
48
  A forwarding interface over the old implementation can be a temporary seam, but
49
- it is not evidence that the interface is a stable public abstraction.
49
+ it is not evidence that the interface is a stable public abstraction. An
50
+ assertion, cast, non-null claim, or unchecked typed decode is not a compatibility
51
+ seam: if old or external data is broader than the new invariant, an adapter must
52
+ parse and return the new representation or explicit failure.
50
53
 
51
54
  For suspected dead code, combine static references, dynamic reachability, and a
52
55
  scoped observation window appropriate to rare jobs, reflection, configuration,
@@ -192,10 +195,15 @@ The protocol is being misused when:
192
195
  - behavior work is hidden inside a structural label;
193
196
  - the route continues after a new human-owned policy question appears;
194
197
  - an intermediate state cannot safely run or be reviewed;
195
- - cleanup continues past the accepted pressure because the code could be nicer.
198
+ - cleanup continues past the accepted pressure because the code could be nicer;
199
+ - validation is preserved while its result is discarded and an assertion claims
200
+ the new representation.
196
201
 
197
202
  ## Source Trace
198
203
 
204
+ - Alexis King, “Parse, don’t validate,” published November 5, 2019, for
205
+ evidence-preserving refinement before execution. The compatibility-seam and
206
+ migration wording is a Developer adaptation.
199
207
  - Sandi Metz, Katrina Owen, and TJ Stankus, *99 Bottles of OOP*, Second
200
208
  Edition, version 2.2.2, 2024: Chapters 3-5, pp. 51-137, especially
201
209
  pp. 58-71, 84-101, and 113-125, on methodical transformations, flocking,
@@ -86,13 +86,17 @@ export function availablePackageSkills(
86
86
  return available;
87
87
  }
88
88
 
89
+ function hasErrorCode(error: unknown, code: string): boolean {
90
+ return isRecord(error) && error.code === code;
91
+ }
92
+
89
93
  export async function skillReferencePaths(skill: Skill): Promise<string[]> {
90
94
  const referencesRoot = resolve(skill.baseDir, "references");
91
95
  let entries: Dirent[];
92
96
  try {
93
97
  entries = await readdir(referencesRoot, { withFileTypes: true });
94
98
  } catch (error) {
95
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
99
+ if (hasErrorCode(error, "ENOENT")) return [];
96
100
  throw error;
97
101
  }
98
102
 
@@ -248,7 +252,7 @@ export async function loadSkillReferencePolicy(
248
252
  try {
249
253
  source = await readFile(policyPath, "utf8");
250
254
  } catch (error) {
251
- if ((error as NodeJS.ErrnoException).code === "ENOENT") {
255
+ if (hasErrorCode(error, "ENOENT")) {
252
256
  if (catalog.length > 0) {
253
257
  throw new Error(
254
258
  `Developer skill ${skill.name} has references but no reference-policy.json.`,
@@ -46,6 +46,36 @@ export type QuestionUpdateStatus =
46
46
  export type ImplementationProfile =
47
47
  | "ordinary"
48
48
  | "behavior-preserving-structure";
49
+ export type InvariantHandling =
50
+ | {
51
+ kind: "not-applicable";
52
+ reason: string;
53
+ }
54
+ | {
55
+ kind: "evidence-preserving-boundary";
56
+ rawRepresentation: string;
57
+ refinedRepresentation: string;
58
+ producer: string;
59
+ failure: string;
60
+ firstEffect: string;
61
+ }
62
+ | {
63
+ kind: "trusted-compiler-gap";
64
+ assertion: string;
65
+ establishedBy: string;
66
+ limitation: string;
67
+ containment: string;
68
+ verification: string;
69
+ };
70
+
71
+ export function formatInvariantHandling(value: InvariantHandling): string {
72
+ if (value.kind === "not-applicable")
73
+ return `not applicable — ${value.reason}`;
74
+ if (value.kind === "evidence-preserving-boundary") {
75
+ return `${value.rawRepresentation} -> ${value.refinedRepresentation} via ${value.producer}; failure=${value.failure}; first effect=${value.firstEffect}`;
76
+ }
77
+ return `trusted compiler gap — ${value.assertion}; established by ${value.establishedBy}; limitation=${value.limitation}; contained at ${value.containment}; check=${value.verification}`;
78
+ }
49
79
 
50
80
  export interface ActivationEvent {
51
81
  protocol: typeof PROTOCOL;
@@ -63,6 +93,7 @@ export interface ImplementationStepContract {
63
93
  movement: string;
64
94
  stopCondition: string;
65
95
  verification: string;
96
+ invariantHandling: InvariantHandling;
66
97
  }
67
98
 
68
99
  export interface RouteAlternative {
@@ -248,6 +279,20 @@ function isStringArray(value: unknown): value is string[] {
248
279
  );
249
280
  }
250
281
 
282
+ function parseArray<T>(
283
+ value: unknown,
284
+ parse: (item: unknown) => T | undefined,
285
+ ): T[] | undefined {
286
+ if (!Array.isArray(value)) return undefined;
287
+ const parsed: T[] = [];
288
+ for (const item of value) {
289
+ const result = parse(item);
290
+ if (result === undefined) return undefined;
291
+ parsed.push(result);
292
+ }
293
+ return parsed;
294
+ }
295
+
251
296
  function isJudgmentStatus(value: unknown): value is JudgmentStatus {
252
297
  return (
253
298
  value === "resolved" ||
@@ -263,6 +308,51 @@ function isImplementationProfile(
263
308
  return value === "ordinary" || value === "behavior-preserving-structure";
264
309
  }
265
310
 
311
+ function parseInvariantHandling(value: unknown): InvariantHandling | undefined {
312
+ if (!isObject(value) || typeof value.kind !== "string") return undefined;
313
+ if (value.kind === "not-applicable") {
314
+ if (typeof value.reason !== "string") return undefined;
315
+ return { kind: value.kind, reason: value.reason };
316
+ }
317
+ if (value.kind === "evidence-preserving-boundary") {
318
+ if (
319
+ typeof value.rawRepresentation !== "string" ||
320
+ typeof value.refinedRepresentation !== "string" ||
321
+ typeof value.producer !== "string" ||
322
+ typeof value.failure !== "string" ||
323
+ typeof value.firstEffect !== "string"
324
+ ) {
325
+ return undefined;
326
+ }
327
+ return {
328
+ kind: value.kind,
329
+ rawRepresentation: value.rawRepresentation,
330
+ refinedRepresentation: value.refinedRepresentation,
331
+ producer: value.producer,
332
+ failure: value.failure,
333
+ firstEffect: value.firstEffect,
334
+ };
335
+ }
336
+ if (value.kind !== "trusted-compiler-gap") return undefined;
337
+ if (
338
+ typeof value.assertion !== "string" ||
339
+ typeof value.establishedBy !== "string" ||
340
+ typeof value.limitation !== "string" ||
341
+ typeof value.containment !== "string" ||
342
+ typeof value.verification !== "string"
343
+ ) {
344
+ return undefined;
345
+ }
346
+ return {
347
+ kind: value.kind,
348
+ assertion: value.assertion,
349
+ establishedBy: value.establishedBy,
350
+ limitation: value.limitation,
351
+ containment: value.containment,
352
+ verification: value.verification,
353
+ };
354
+ }
355
+
266
356
  function parseImplementationStep(
267
357
  value: unknown,
268
358
  ): ImplementationStepContract | undefined {
@@ -274,10 +364,20 @@ function parseImplementationStep(
274
364
  ) {
275
365
  return undefined;
276
366
  }
367
+ const invariantHandling =
368
+ value.invariantHandling === undefined
369
+ ? {
370
+ kind: "not-applicable" as const,
371
+ reason:
372
+ "Legacy implementation route predates the invariant-handling declaration.",
373
+ }
374
+ : parseInvariantHandling(value.invariantHandling);
375
+ if (!invariantHandling) return undefined;
277
376
  return {
278
377
  movement: value.movement,
279
378
  stopCondition: value.stopCondition,
280
379
  verification: value.verification,
380
+ invariantHandling,
281
381
  };
282
382
  }
283
383
 
@@ -622,19 +722,21 @@ export function normalizeDeveloperEvent(
622
722
  ) {
623
723
  return undefined;
624
724
  }
625
- const consideredAlternatives = Array.isArray(value.consideredAlternatives)
626
- ? value.consideredAlternatives.map(parseRouteAlternative)
627
- : [];
628
- if (consideredAlternatives.some((alternative) => !alternative))
629
- return undefined;
630
- const referenceRoutes = Array.isArray(value.referenceRoutes)
631
- ? value.referenceRoutes.map(parseReferencePolicyRoute)
632
- : [];
633
- if (referenceRoutes.some((route) => !route)) return undefined;
634
- const loadedReferences = Array.isArray(value.loadedReferences)
635
- ? value.loadedReferences.map(parseReferenceLoad)
636
- : [];
637
- if (loadedReferences.some((reference) => !reference)) return undefined;
725
+ const consideredAlternatives =
726
+ value.consideredAlternatives === undefined
727
+ ? []
728
+ : parseArray(value.consideredAlternatives, parseRouteAlternative);
729
+ if (!consideredAlternatives) return undefined;
730
+ const referenceRoutes =
731
+ value.referenceRoutes === undefined
732
+ ? []
733
+ : parseArray(value.referenceRoutes, parseReferencePolicyRoute);
734
+ if (!referenceRoutes) return undefined;
735
+ const loadedReferences =
736
+ value.loadedReferences === undefined
737
+ ? []
738
+ : parseArray(value.loadedReferences, parseReferenceLoad);
739
+ if (!loadedReferences) return undefined;
638
740
  return {
639
741
  protocol: PROTOCOL,
640
742
  kind: "route",
@@ -643,15 +745,15 @@ export function normalizeDeveloperEvent(
643
745
  target: value.target,
644
746
  reason: value.reason,
645
747
  knownEvidence: value.knownEvidence,
646
- consideredAlternatives: consideredAlternatives as RouteAlternative[],
748
+ consideredAlternatives,
647
749
  availableReferences: value.availableReferences ?? [],
648
- referenceRoutes: referenceRoutes as ReferencePolicyRoute[],
750
+ referenceRoutes,
649
751
  referenceExemptionCriteria:
650
752
  value.referenceExemptionCriteria === undefined
651
753
  ? undefined
652
754
  : parseReferencePolicyExemption(value.referenceExemptionCriteria),
653
755
  referencePolicySha256: value.referencePolicySha256,
654
- loadedReferences: loadedReferences as ReferenceLoad[],
756
+ loadedReferences,
655
757
  targetQuestionId: value.targetQuestionId,
656
758
  methodLocation: value.methodLocation,
657
759
  executionProfile: value.executionProfile,
@@ -698,21 +800,21 @@ export function normalizeDeveloperEvent(
698
800
  return undefined;
699
801
  }
700
802
 
701
- const openedQuestions = value.openedQuestions.map(parsePendingQuestion);
702
- if (openedQuestions.some((question) => !question)) return undefined;
703
- const referenceBasis = Array.isArray(value.referenceBasis)
704
- ? value.referenceBasis.map(parseReferenceBasis)
705
- : [];
706
- if (referenceBasis.some((basis) => !basis)) return undefined;
707
- if (
708
- value.questionUpdates !== undefined &&
709
- !Array.isArray(value.questionUpdates)
710
- )
711
- return undefined;
712
- const questionUpdates = Array.isArray(value.questionUpdates)
713
- ? value.questionUpdates.map(parseQuestionUpdate)
714
- : [];
715
- if (questionUpdates.some((update) => !update)) return undefined;
803
+ const openedQuestions = parseArray(
804
+ value.openedQuestions,
805
+ parsePendingQuestion,
806
+ );
807
+ if (!openedQuestions) return undefined;
808
+ const referenceBasis =
809
+ value.referenceBasis === undefined
810
+ ? []
811
+ : parseArray(value.referenceBasis, parseReferenceBasis);
812
+ if (!referenceBasis) return undefined;
813
+ const questionUpdates =
814
+ value.questionUpdates === undefined
815
+ ? []
816
+ : parseArray(value.questionUpdates, parseQuestionUpdate);
817
+ if (!questionUpdates) return undefined;
716
818
  return {
717
819
  protocol: PROTOCOL,
718
820
  kind: "judgment",
@@ -722,13 +824,13 @@ export function normalizeDeveloperEvent(
722
824
  status: value.status,
723
825
  result: value.result,
724
826
  basis: value.basis,
725
- referenceBasis: referenceBasis as ReferenceBasis[],
827
+ referenceBasis,
726
828
  referenceExemption:
727
829
  value.referenceExemption === undefined
728
830
  ? undefined
729
831
  : parseReferenceExemption(value.referenceExemption),
730
- openedQuestions: openedQuestions as PendingQuestion[],
731
- questionUpdates: questionUpdates as QuestionUpdate[],
832
+ openedQuestions,
833
+ questionUpdates,
732
834
  artifacts: value.artifacts,
733
835
  changedArtifacts:
734
836
  typeof value.changedArtifacts === "boolean"
package/extensions/tui.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  } from "@earendil-works/pi-tui";
23
23
 
24
24
  import {
25
+ formatInvariantHandling,
25
26
  protocolState,
26
27
  type ChoiceResponseField,
27
28
  type ChoiceResponseOption,
@@ -1416,6 +1417,10 @@ export class DeveloperHistoryDetailPanel {
1416
1417
  addField("movement", route.implementationStep.movement);
1417
1418
  addField("stop condition", route.implementationStep.stopCondition);
1418
1419
  addField("verification", route.implementationStep.verification);
1420
+ addField(
1421
+ "invariant handling",
1422
+ formatInvariantHandling(route.implementationStep.invariantHandling),
1423
+ );
1419
1424
  }
1420
1425
  addValues("known evidence", route.knownEvidence);
1421
1426
  addValues(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hobin/developer",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Branch-aware judgment routing, evidence gates, and focused development skills for Pi.",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package", "pi", "developer", "skills"],
@@ -78,8 +78,12 @@ states, transitions, or policy decisions invalidate the model.
78
78
  5. Select one specialized model only when contract, relation, time, exhaustive
79
79
  evidence, or search has an independent artifact and stop condition.
80
80
  6. State the ability lost and guarantee gained by the chosen representation.
81
- 7. Place each guarantee at the appropriate type, boundary, validation, test,
82
- property, proof, model check, or human decision.
81
+ 7. Place each guarantee at the appropriate type, boundary, parser, constructor,
82
+ database constraint, test, property, proof, model check, or human decision.
83
+ A validation result that carries no refined value and an unchecked assertion,
84
+ cast, non-null claim, or typed deserialization target cannot own a
85
+ representable-state guarantee. When raw and admitted domains differ, require
86
+ an evidence-bearing transition without designing its API.
83
87
  8. Derive counterexamples and verification targets without designing the API.
84
88
 
85
89
  ## Missing Evidence
@@ -134,8 +134,10 @@ to remain aligned.
134
134
 
135
135
  For every important rule, name the cheapest trustworthy owner and evidence target:
136
136
 
137
- - type for representable-state restrictions;
138
- - constructor or runtime validation for hostile input;
137
+ - type or abstract representation for representable-state restrictions, but only
138
+ when every construction path establishes the restriction;
139
+ - parser or smart constructor for hostile or broader input, returning the
140
+ invariant-carrying value or explicit failure rather than discarding the check;
139
141
  - contract for caller/callee meaning;
140
142
  - database constraint for persisted relational facts;
141
143
  - state transition owner for history-sensitive rules;
@@ -144,7 +146,11 @@ For every important rule, name the cheapest trustworthy owner and evidence targe
144
146
  - model, solver, or proof for bounded exhaustive relationships;
145
147
  - human decision for policy.
146
148
 
147
- An unchecked statement may guide design, but it is not evidence.
149
+ An unchecked statement may guide design, but it is not evidence. Validation that
150
+ returns only success, `Unit`, or `Bool` cannot by itself place a guarantee at a
151
+ stronger type, and an assertion, cast, non-null claim, ignored conversion result,
152
+ or typed deserialization target does not repair that gap. Record the required
153
+ raw-to-refined transition and hand its concrete caller surface to `sketch`.
148
154
 
149
155
  ## Model Artifact
150
156
 
@@ -177,6 +183,10 @@ Separate rather than expanding this reference when:
177
183
 
178
184
  ## Source Trace
179
185
 
186
+ - Alexis King, “Parse, don’t validate,” published November 5, 2019, for the
187
+ distinction between checks that discard learned information and fallible
188
+ transitions that return a refined representation. Concrete parser and
189
+ smart-constructor surfaces remain owned by `sketch`.
180
190
  - Hillel Wayne, *Logic for Programmers*, v0.14.0, 2026-05-04:
181
191
  Chapters 2-4, pp. 5-46, for predicates, quantifiers, ability/guarantee,
182
192
  specifications, and logic/runtime boundaries. Recorded beta defects are
@@ -50,10 +50,13 @@ sketch must be inspectable as an implementation shape, not only narrated in
50
50
  paragraphs. Produce:
51
51
 
52
52
  1. a compact case/check table;
53
- 2. concrete data or state definitions;
53
+ 2. concrete data or state definitions, including raw and refined forms when an
54
+ input's provenance is broader than the accepted domain;
54
55
  3. wishful top-level code, pseudocode, or an interaction skeleton in a fenced
55
56
  code block;
56
57
  4. a wished-interface table with contract, owner, hidden detail, and stop check;
58
+ when raw and refined forms differ, show the parser or smart constructor whose
59
+ success returns the refined value and whose failure precedes dependent effects;
57
60
  5. a small ordered implementation queue and explicitly deferred abstractions;
58
61
  6. an ASCII flow, relation map, state transition, or boundary diagram whenever
59
62
  two or more components, states, or collaborations are materially related.
@@ -78,7 +81,10 @@ routing to the caller.
78
81
 
79
82
  Finish when the first implementation item is small enough to execute and check,
80
83
  and non-local or invariant-bearing candidates are explicit rather than silently
81
- assumed. `resolved` is not valid for a prose-only sketch: the output must show
84
+ assumed. If broader, external, persisted, legacy, or otherwise less-trusted data
85
+ enters the surface, `resolved` also requires an evidence-preserving transition to
86
+ the domain representation; validation followed by an assertion is not such a
87
+ transition. `resolved` is not valid for a prose-only sketch: the output must show
82
88
  the code or interaction skeleton and the checks that make the first item
83
89
  executable. Revisit when implementation evidence breaks the ownership or
84
90
  data-flow assumptions.
@@ -87,7 +93,9 @@ data-flow assumptions.
87
93
 
88
94
  1. Choose the strongest available source of intent and state its confidence.
89
95
  2. State the design unit's purpose in the user's language.
90
- 3. Derive relevant data or state definitions and their ownership pressure.
96
+ 3. Derive relevant data or state definitions and their ownership pressure. Mark
97
+ provenance and the raw-to-refined boundary whenever callers cannot already
98
+ supply an invariant-carrying value. Treat unchecked narrowing as no evidence.
91
99
  4. List representative cases before choosing code shape.
92
100
  5. Name each independently unresolved design question and select only its routed
93
101
  extension; keep disputed meaning in `model`.
@@ -4,12 +4,12 @@
4
4
  {
5
5
  "id": "data-driven-design",
6
6
  "question": "How do accepted data meanings and examples determine the first executable function surface?",
7
- "trigger": "Product information, variants, examples, or tests should determine the function skeleton, and no narrower template, composition, generation, or accumulator question is primary.",
7
+ "trigger": "Product information, variants, examples, or tests should determine the function skeleton, and no narrower template, composition, generation, accumulator, or evidence-preserving boundary question is primary.",
8
8
  "method_step": "derive data definition, interpretation, examples, purpose, template, completed shape, and first executable item",
9
9
  "references": ["references/data-driven-design.md"],
10
10
  "artifacts": ["the six ordered design artifacts", "repository contradictions mapped to those artifacts", "a bounded first item and propagation check"],
11
11
  "stop": "Accepted data and behavior examples constrain an executable skeleton with contradictions and compatibility visible.",
12
- "separate_when": "Template shape, composition, earned abstraction, generated problems, or accumulated knowledge has an independent artifact."
12
+ "separate_when": "Template shape, composition, earned abstraction, generated problems, accumulated knowledge, or raw-to-refined construction has an independent artifact."
13
13
  },
14
14
  {
15
15
  "id": "data-shape-template",
@@ -66,6 +66,16 @@
66
66
  "stop": "The invariant explains initial, step, and final states and every semantic/resource delta is explicit.",
67
67
  "separate_when": "Problems are generated, carried data is persistent product history, or a concrete cache surface needs review."
68
68
  },
69
+ {
70
+ "id": "evidence-preserving-boundary",
71
+ "question": "How does less-trusted or less-structured input become a domain value that carries the established invariant?",
72
+ "trigger": "External, serialized, persisted, legacy, foreign, unknown, nullable, or overly broad input must become an accepted domain value; checks are repeated, return no refined value, occur after effects, or are followed by an assertion, cast, non-null claim, unchecked decode, or public raw construction.",
73
+ "method_step": "derive raw provenance, refined representation, parser or smart-constructor success/failure, construction owner, first-effect boundary, escape audit, and any bounded compiler gap",
74
+ "references": ["references/evidence-preserving-boundaries.md"],
75
+ "artifacts": ["a raw-to-refined boundary pipeline", "a parser/smart-constructor and failure contract", "a construction/escape, effect-order, and trusted-compiler-gap audit"],
76
+ "stop": "Every core construction path returns the invariant-carrying representation or explicit failure before dependent effects, with no unchecked narrowing or public raw-constructor bypass.",
77
+ "separate_when": "Admitted values or failure policy are disputed, alternate representations/public laws are independently consequential, a valid domain event changes type, or old/new compatibility remains unsettled."
78
+ },
69
79
  {
70
80
  "id": "design-levels",
71
81
  "question": "Which vocabulary, owner, and dependency direction make this caller contract truthful?",
@@ -74,7 +84,7 @@
74
84
  "references": ["references/design-levels-and-boundaries.md"],
75
85
  "artifacts": ["a caller-purpose and level map", "contract/owner/hidden choice/assumptions", "an alternate implementation or falsifier"],
76
86
  "stop": "Dependencies use truthful vocabulary, guarantees have owners, and lower-level change does not force unrelated callers.",
77
- "separate_when": "Representation, closure, process, state, dispatch, conversion, language, runtime, responsibility, or variation has its own output."
87
+ "separate_when": "Evidence-preserving input refinement, representation, closure, process, state, dispatch, conversion, language, runtime, responsibility, or variation has its own output."
78
88
  },
79
89
  {
80
90
  "id": "representation-barrier",
@@ -0,0 +1,200 @@
1
+ # Evidence-Preserving Boundaries
2
+
3
+ Use this reference when less-trusted or less-structured input must become a domain
4
+ value whose type or abstract representation carries an accepted invariant. The
5
+ input may be bytes, JSON, form values, command-line arguments, database rows,
6
+ legacy records, framework payloads, foreign values, or an overly broad in-memory
7
+ type. Parsing here means any fallible transformation from a weaker
8
+ representation to a stronger one; it is not limited to text.
9
+
10
+ ## Information Must Survive The Check
11
+
12
+ Two operations may perform the same runtime checks while establishing different
13
+ contracts:
14
+
15
+ ```text
16
+ validate : Raw -> Result<Unit, BoundaryError>
17
+ parse : Raw -> Result<Domain, BoundaryError>
18
+ ```
19
+
20
+ `validate` reports only success or failure. Unless its result narrows the value
21
+ in a scope the compiler preserves, later code still receives `Raw` and must
22
+ repeat the check, trust a comment, or assert `Domain`. `parse` returns the more
23
+ precise value and makes the learned information necessary for execution.
24
+
25
+ Treat these as evidence-destroying by default:
26
+
27
+ ```text
28
+ validate(raw); cast raw to Domain
29
+ JSON.decode(bytes) as Domain
30
+ lookup(key)! where absence has domain meaning
31
+ construct Domain through public raw fields
32
+ ```
33
+
34
+ A type assertion, cast, non-null assertion, ignored conversion result, or typed
35
+ deserialization target is not evidence that an invariant holds.
36
+
37
+ ## Draw The Refinement Boundary
38
+
39
+ Derive this pipeline before choosing library syntax:
40
+
41
+ ```text
42
+ external / raw / legacy representation
43
+ |
44
+ | parse / refine / smart constructor
45
+ | failure is explicit here
46
+ v
47
+ invariant-carrying domain value
48
+ |
49
+ | total core operations
50
+ v
51
+ domain effects
52
+ ```
53
+
54
+ Record:
55
+
56
+ ```text
57
+ raw representation and provenance
58
+ accepted domain representation
59
+ invariant learned at the transition
60
+ parser or smart-constructor signature
61
+ success and failure result
62
+ single construction owner
63
+ first domain effect allowed after success
64
+ legacy, persistence, and re-entry paths
65
+ ```
66
+
67
+ Push the proof burden toward the earliest boundary that has enough information,
68
+ but no further. Authorization or a small resource guard may precede expensive
69
+ parsing when abuse resistance requires it; those checks must not perform the
70
+ domain mutation that depends on parsed input. Parsing may use several passes or
71
+ select a later parser from an earlier discriminator as long as execution does
72
+ not consume partially parsed domain data.
73
+
74
+ ## Choose A Stronger Representation
75
+
76
+ Prefer the most precise representation the current language and repository can
77
+ reasonably support:
78
+
79
+ - a product type when required fields must be present together;
80
+ - a sum type when cases have different meanings or fields;
81
+ - a non-empty, bounded, normalized, branded, opaque, or capability-bearing type
82
+ when that restriction removes a real downstream branch;
83
+ - a map or set when uniqueness or duplicate policy is accepted;
84
+ - an abstract type with a smart constructor when the host type system cannot
85
+ directly encode the invariant.
86
+
87
+ Do not call a narrower representation better until the excluded ability is known
88
+ to be unnecessary. The accepted domain comes from `model`; this route owns the
89
+ boundary that constructs it.
90
+
91
+ ## Wished Interface
92
+
93
+ Use the target language's ordinary result and error conventions. The important
94
+ shape is that success carries `Domain`:
95
+
96
+ ```text
97
+ parseOrder : UnknownInput -> Result<Order, OrderInputError>
98
+ placeOrder : Order -> Effect<PlacementResult>
99
+
100
+ handle(input):
101
+ order <- parseOrder(input)
102
+ placeOrder(order)
103
+ ```
104
+
105
+ Throwing at an application edge, returning an option/either/result, or using a
106
+ checked constructor can all be valid. A predicate or assertion function can also
107
+ preserve information when the language genuinely narrows the checked value in
108
+ the only scope where it is consumed. What is not valid is checking one value and
109
+ then separately claiming a stronger type without a compiler-visible or
110
+ abstraction-visible connection.
111
+
112
+ ## Construction And Escape Audit
113
+
114
+ List every way `Domain` can enter the core:
115
+
116
+ | Path | Input provenance | Establishes invariant by | Produces | Bypass risk |
117
+ | --- | --- | --- | --- | --- |
118
+ | public parser | external raw value | complete boundary checks | `Domain` or failure | expected path |
119
+ | smart constructor | broad in-memory value | constructor checks | abstract `Domain` or failure | expected path |
120
+ | deserializer | persisted or wire value | parser plus compatibility policy | `Domain` or failure | unchecked typed decode |
121
+ | internal constructor | already refined fields | private construction law | `Domain` | public/raw export |
122
+ | test factory | test data | public or equivalent checked path | `Domain` | unrealistic invalid fixtures |
123
+
124
+ A claimed invariant is only as strong as its weakest construction path. Search
125
+ for raw constructors, object literals, reflection, ORM hydration, generated
126
+ adapters, test factories, casts, suppressions, and legacy re-entry points.
127
+
128
+ ## Trusted Compiler Gaps
129
+
130
+ Sometimes a complete check establishes a fact that the host type system cannot
131
+ retain. Do not spread assertions to compensate. Isolate the smallest exception
132
+ inside the one parser or smart-constructor owner and record:
133
+
134
+ ```text
135
+ exact assertion or unsafe operation
136
+ fact already established
137
+ complete check or trusted contract that established it
138
+ why ordinary narrowing cannot express the fact
139
+ containment boundary
140
+ falsifying test, type check, or structural inspection
141
+ ```
142
+
143
+ Literal-preservation syntax and framework types already guaranteed by a runtime
144
+ schema are not the same as asserting hostile input into a domain type. The
145
+ burden is still on the exception to name its prior evidence. If that evidence is
146
+ missing, return to the parser design instead of documenting a wish.
147
+
148
+ ## Shotgun And Effect Check
149
+
150
+ Reject a design when parsing or validation is mixed through processing code:
151
+
152
+ - consumers repeat checks for the same invariant;
153
+ - a late branch can discover malformed input after writes, sends, or mutations;
154
+ - every core function reserves an "impossible" failure despite an earlier check;
155
+ - compatibility adapters emit partially refined values;
156
+ - normalized and raw forms remain mutable and can drift apart.
157
+
158
+ Parse before the first domain effect. If staging or streaming makes one complete
159
+ up-front parse impractical, define an explicit phase or transaction boundary and
160
+ ensure each effect consumes a value whose required invariant is already carried
161
+ by that phase's representation.
162
+
163
+ ## Artifact
164
+
165
+ ```text
166
+ Raw representation and provenance:
167
+ Refined domain representation:
168
+ Invariant gained and ability lost:
169
+ Parser/smart-constructor signature:
170
+ Failure representation:
171
+ Construction owner and escape audit:
172
+ First domain effect after success:
173
+ Legacy/persistence/re-entry handling:
174
+ Trusted compiler gaps, or none:
175
+ Negative, bypass, and effect-order checks:
176
+ ```
177
+
178
+ ## Stop And Separation
179
+
180
+ Stop when every core construction path either produces the refined domain value
181
+ through the declared parser/constructor or fails explicitly, invalid input is
182
+ rejected before the domain effect that depends on it, and no unchecked narrowing
183
+ or public raw constructor claims the invariant.
184
+
185
+ Return to `model` when the admitted values or failure policy are disputed. Use
186
+ `representation-barrier` when alternate internal representations and public laws
187
+ are independently consequential, `type-transition` when an accepted domain
188
+ event transforms one valid domain type into another, and `contract-replacement`
189
+ when old and new boundary compatibility remains unsettled.
190
+
191
+ ## Source Trace
192
+
193
+ - Alexis King, “Parse, don’t validate,” published November 5, 2019: the
194
+ `NonEmpty` example, validation-versus-parsing return-type contrast, boundary
195
+ parsing and shotgun-parsing discussion, practical duplicate-key example, and
196
+ guidance on precise datatypes, proof placement, multi-pass parsing,
197
+ denormalization, abstract datatypes, and bounded exceptions.
198
+ - The construction-path audit, trusted-compiler-gap record, effect-order check,
199
+ repository compatibility paths, and language-neutral assertion taxonomy are
200
+ Developer adaptations of that source's type-driven design argument.
@@ -74,8 +74,11 @@ after code changes, new evidence, changed claims, or source-provenance changes.
74
74
  1. Extract the exact claims from the request, accepted decisions, implementation
75
75
  report, invariant, or model.
76
76
  2. Attach concrete evidence to each claim and classify its strength.
77
- 3. Check constraints, forbidden cases, transitions, callers, and abstraction
78
- stop checks when they are part of the claim.
77
+ 3. Check constraints, forbidden cases, transitions, callers, abstraction stop
78
+ checks, and every construction or re-entry path for invariant-carrying values
79
+ when they are part of the claim. Validation followed by an assertion, a
80
+ public raw constructor, unchecked deserialization, or a domain effect before
81
+ parsing is a pass-but-wrong shape, not type evidence.
79
82
  4. Distinguish verifier execution from verifier relevance.
80
83
  5. Ask what wrong implementation could still pass and name its concrete shape.
81
84
  6. Narrow any passing claim that is broader than its evidence.
@@ -26,7 +26,7 @@ Separate independently falsifiable claims:
26
26
  - user or caller behavior;
27
27
  - forbidden and boundary cases;
28
28
  - data multiplicity and preservation;
29
- - type or API contract;
29
+ - type or API contract, including raw-to-refined construction and re-entry paths;
30
30
  - state transition and history;
31
31
  - representation or ownership boundary;
32
32
  - source/version compatibility;
@@ -102,10 +102,17 @@ calibrations by failure mechanism rather than by book.
102
102
  | database schema behavior is preserved | row content matches | labels or function-valued predicates changed | inspect schema and execute predicates/integrity checks |
103
103
  | temporal implementation is correct | each enum state is reachable | stale or duplicate event commits twice | ordered trace with stale, retry, duplicate, and concurrency |
104
104
  | abstraction is safe | public tests pass | callers still depend on fields, load order, or hidden default | leak search, fake variant, and real caller rewrite |
105
+ | domain type carries its invariant | happy input passes and typecheck is green | validation returns no refined value, then a cast/assertion claims the domain type | inspect every construction path; require parser/smart-constructor success to return the domain value |
106
+ | invalid input cannot affect domain state | negative parser test passes | a write, send, mutation, or partial update happens before parsing completes | effect-order integration check with invalid and partially valid input |
107
+ | construction boundary is closed | public parser tests pass | raw constructor, ORM hydration, test factory, or legacy adapter bypasses the parser | constructor/export/re-entry search plus a direct-bypass type or runtime check |
105
108
  | package/source compatibility holds | workspace tests pass | packed artifact or installed version lacks changed files | inspect tarball/fresh install and bind provenance |
106
109
 
107
110
  A verifier for one row does not support another. In particular, location,
108
111
  residual, representation, and downstream tolerances are not interchangeable.
112
+ A typecheck proves only that the program follows the declared types; an
113
+ unchecked narrowing operation can make a false declaration typecheck. Pair
114
+ compile-time evidence with construction-path and boundary evidence whenever the
115
+ type itself is part of the claim.
109
116
 
110
117
  ## Formal And Search Result Boundaries
111
118
 
@@ -165,6 +172,10 @@ Unverified claims and residual risk:
165
172
 
166
173
  ## Source Trace
167
174
 
175
+ - Alexis King, “Parse, don’t validate,” published November 5, 2019, for the
176
+ distinction between a check that discards information and a parser that
177
+ returns a refined value, plus the parsing-before-execution boundary. The
178
+ construction-path and effect-order verifier rows are Developer adaptations.
168
179
  - Hillel Wayne, *Logic for Programmers*, v0.14.0:
169
180
  Chapters 3-6 and 9-11, pp. 23-155, for logical/runtime preservation, partial
170
181
  specifications, generated properties, proof limits, bounded models, and solver