@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 +5 -1
- package/REFERENCE_ROUTING.md +1 -1
- package/extensions/developer.ts +161 -6
- package/extensions/references/behavior-preserving-structural-change.md +10 -2
- package/extensions/skills.ts +6 -2
- package/extensions/state.ts +136 -34
- package/extensions/tui.ts +5 -0
- package/package.json +1 -1
- package/skills/model/SKILL.md +6 -2
- package/skills/model/references/problem-modeling.md +13 -3
- package/skills/sketch/SKILL.md +11 -3
- package/skills/sketch/reference-policy.json +13 -3
- package/skills/sketch/references/evidence-preserving-boundaries.md +200 -0
- package/skills/verify/SKILL.md +5 -2
- package/skills/verify/references/verifier-selection-and-pass-but-wrong.md +12 -1
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 |
|
package/REFERENCE_ROUTING.md
CHANGED
|
@@ -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 |
|
package/extensions/developer.ts
CHANGED
|
@@ -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,
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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,
|
package/extensions/skills.ts
CHANGED
|
@@ -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
|
|
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
|
|
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.`,
|
package/extensions/state.ts
CHANGED
|
@@ -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 =
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const referenceRoutes =
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
|
748
|
+
consideredAlternatives,
|
|
647
749
|
availableReferences: value.availableReferences ?? [],
|
|
648
|
-
referenceRoutes
|
|
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
|
|
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 =
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
)
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (questionUpdates
|
|
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
|
|
827
|
+
referenceBasis,
|
|
726
828
|
referenceExemption:
|
|
727
829
|
value.referenceExemption === undefined
|
|
728
830
|
? undefined
|
|
729
831
|
: parseReferenceExemption(value.referenceExemption),
|
|
730
|
-
openedQuestions
|
|
731
|
-
questionUpdates
|
|
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
package/skills/model/SKILL.md
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
package/skills/sketch/SKILL.md
CHANGED
|
@@ -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.
|
|
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
|
|
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,
|
|
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": "
|
|
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.
|
package/skills/verify/SKILL.md
CHANGED
|
@@ -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,
|
|
78
|
-
|
|
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
|