create-pathfinder 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -0
- package/package.json +1 -1
- package/skills/learn-codebase/SKILL.md +188 -17
- package/skills/learn-feature/SKILL.md +136 -15
- package/skills/map-system/SKILL.md +293 -0
- package/skills/render-artifact/SKILL.md +187 -0
- package/skills/render-artifact/engine/bin/render.mjs +225 -0
- package/skills/render-artifact/engine/deliver.mjs +197 -0
- package/skills/render-artifact/engine/doctor.mjs +96 -0
- package/skills/render-artifact/engine/examples/diagram.json +223 -0
- package/skills/render-artifact/engine/examples/lesson.json +242 -0
- package/skills/render-artifact/engine/references/determinism.md +71 -0
- package/skills/render-artifact/engine/references/specification.md +149 -0
- package/skills/render-artifact/engine/references/validation.md +268 -0
- package/skills/render-artifact/engine/render/behavior.mjs +128 -0
- package/skills/render-artifact/engine/render/diagram.mjs +342 -0
- package/skills/render-artifact/engine/render/escape.mjs +34 -0
- package/skills/render-artifact/engine/render/graph/behavior.mjs +394 -0
- package/skills/render-artifact/engine/render/graph/draw.mjs +204 -0
- package/skills/render-artifact/engine/render/graph/interaction.mjs +174 -0
- package/skills/render-artifact/engine/render/graph/layout.mjs +698 -0
- package/skills/render-artifact/engine/render/graph/style.mjs +200 -0
- package/skills/render-artifact/engine/render/graph/width.mjs +204 -0
- package/skills/render-artifact/engine/render/index.mjs +50 -0
- package/skills/render-artifact/engine/render/lesson.mjs +294 -0
- package/skills/render-artifact/engine/render/shell.mjs +275 -0
- package/skills/render-artifact/engine/render/theme.mjs +592 -0
- package/skills/render-artifact/engine/schemas/common.schema.json +101 -0
- package/skills/render-artifact/engine/schemas/diagram.schema.json +176 -0
- package/skills/render-artifact/engine/schemas/lesson.schema.json +210 -0
- package/skills/render-artifact/engine/validate/composition.mjs +395 -0
- package/skills/render-artifact/engine/validate/diagnostics.mjs +83 -0
- package/skills/render-artifact/engine/validate/diagram-parts.mjs +68 -0
- package/skills/render-artifact/engine/validate/evidence.mjs +302 -0
- package/skills/render-artifact/engine/validate/index.mjs +132 -0
- package/skills/render-artifact/engine/validate/jsonschema.mjs +312 -0
- package/skills/render-artifact/engine/validate/structural.mjs +241 -0
- package/skills/render-artifact/engine/verification.mjs +76 -0
- package/skills/render-artifact/engine/version.mjs +24 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# The specification
|
|
2
|
+
|
|
3
|
+
One shared contract, and one kind-specific schema selected by `kind`.
|
|
4
|
+
|
|
5
|
+
Everything here is producer-supplied *content*. Nothing here is presentation.
|
|
6
|
+
If you find yourself wanting a field for a colour, a class, a width, a
|
|
7
|
+
coordinate, an icon, a template, or a theme, the answer is that the renderer
|
|
8
|
+
owns it — and the schema will reject the field rather than ignore it, so the
|
|
9
|
+
attempt fails loudly instead of silently doing nothing.
|
|
10
|
+
|
|
11
|
+
## The shared contract
|
|
12
|
+
|
|
13
|
+
`schemas/common.schema.json`.
|
|
14
|
+
|
|
15
|
+
| Field | Required | What it is |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `schema_version` | yes | `"1.0"`. The contract this specification is written against. |
|
|
18
|
+
| `kind` | yes | Selects the kind schema and the renderer. `lesson` and `diagram`. |
|
|
19
|
+
| `artifact` | yes | Metadata about the artifact: `title`, and optionally `subtitle`, `summary`, `locale`. |
|
|
20
|
+
| `source` | see below | Where the claims come from: `repo`, `commit`, and optionally `generated_at`. |
|
|
21
|
+
|
|
22
|
+
`source` is required by the `lesson` kind unconditionally. For `diagram` it is
|
|
23
|
+
conditional on `provenance`, and the condition is written into
|
|
24
|
+
`diagram.schema.json` rather than into the shared contract — see
|
|
25
|
+
[`provenance`](#provenance) below. The shared `source` definition itself is
|
|
26
|
+
unchanged and is not loosened: a kind that declares a source declares a complete
|
|
27
|
+
one.
|
|
28
|
+
|
|
29
|
+
### `provenance`
|
|
30
|
+
|
|
31
|
+
`diagram` only. `"derived"` or `"proposed"`, and required — there is no default,
|
|
32
|
+
because a diagram that did not say would have a trust level assigned to it by the
|
|
33
|
+
engine.
|
|
34
|
+
|
|
35
|
+
- **`derived`** maps what the repository asserts about itself at the declared
|
|
36
|
+
commit. `source` is required, every node and edge carries at least one
|
|
37
|
+
citation, a group, path or view carrying `summary` or `note` prose carries one
|
|
38
|
+
too, and `artifact.summary` is not permitted.
|
|
39
|
+
- **`proposed`** describes an intended system. Citations are optional, and every
|
|
40
|
+
one supplied is still resolved. With no `source` at all, citations are refused
|
|
41
|
+
rather than ignored, and the evidence layer is reported as not run.
|
|
42
|
+
|
|
43
|
+
`validation.md` has the full table and the diagnostics. The short version: what
|
|
44
|
+
you may assert is decided by what you are willing to cite, and the renderer's
|
|
45
|
+
wording follows from that rather than from anything you can ask for.
|
|
46
|
+
|
|
47
|
+
### `source`
|
|
48
|
+
|
|
49
|
+
- `repo` identifies the repository the evidence belongs to, so a diagnostic can
|
|
50
|
+
say whose commit is missing.
|
|
51
|
+
- `commit` is a full or abbreviated Git object name. **Evidence resolves
|
|
52
|
+
against this and nothing else** — never the working tree.
|
|
53
|
+
- `generated_at` is optional, supplied by the producer, and part of the
|
|
54
|
+
deterministic input. The renderer never reads a clock, so if a timestamp
|
|
55
|
+
appears in an artifact, it came from here.
|
|
56
|
+
|
|
57
|
+
There is no `theme`, no `theme_default`, and no presentation default of any
|
|
58
|
+
kind. Those belong to the renderer, and the reader's own preference beats both.
|
|
59
|
+
|
|
60
|
+
### Evidence
|
|
61
|
+
|
|
62
|
+
One citation shape, used identically wherever evidence appears, in this kind and
|
|
63
|
+
every kind that follows:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{ "path": "packages/create-pathfinder/src/kit.mjs", "lines": [97, 105] }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `path` — repository-relative, forward-slashed. Never absolute.
|
|
70
|
+
- `lines` — optional inclusive `[start, end]`, 1-based, and must lie within that
|
|
71
|
+
file at the resolved commit.
|
|
72
|
+
- `commit` — optional, overrides `source.commit` for this citation alone. Use it
|
|
73
|
+
when one claim is about a different point in history, not to work around a
|
|
74
|
+
citation that will not resolve.
|
|
75
|
+
|
|
76
|
+
## The `lesson` kind
|
|
77
|
+
|
|
78
|
+
`schemas/lesson.schema.json`.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
lesson
|
|
82
|
+
objectives? string[]
|
|
83
|
+
modules module[] one or many — nothing branches on the count
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`learn-feature` supplies one module. `learn-codebase` supplies many. There is no
|
|
87
|
+
consumer-specific field, no mode flag, and no escape hatch: the same fields
|
|
88
|
+
serve both, and a change made to suit one has to be justified for the other.
|
|
89
|
+
|
|
90
|
+
### Module
|
|
91
|
+
|
|
92
|
+
| Field | Required | Notes |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| `id` | yes | Lowercase, digits and hyphens. Becomes a link target, so it is unique across the artifact. |
|
|
95
|
+
| `title` | yes | |
|
|
96
|
+
| `summary` | no | |
|
|
97
|
+
| `requires` | no | Module ids this builds on. Edges must resolve and must not cycle. A flat list with no edges is legal, and is the single-module case. |
|
|
98
|
+
| `sections` | yes | At least one. |
|
|
99
|
+
|
|
100
|
+
### Sections
|
|
101
|
+
|
|
102
|
+
Discriminated by `type`.
|
|
103
|
+
|
|
104
|
+
**`prose`** — `body` paragraphs. For explanation that carries no claim needing
|
|
105
|
+
a citation.
|
|
106
|
+
|
|
107
|
+
**`concept`** — `title`, `body`, and `evidence`. A claim about the source.
|
|
108
|
+
`evidence` is required and must be non-empty; a concept citing nothing fails the
|
|
109
|
+
evidence layer, because an uncited claim is the renderer asserting domain
|
|
110
|
+
content, which it must never do.
|
|
111
|
+
|
|
112
|
+
**`code`** — `language`, `lines`, optional `title`, `caption`, `first_line`, and
|
|
113
|
+
`evidence`. Lines are emitted verbatim and escaped. `language` labels the
|
|
114
|
+
excerpt for the reader; it selects no highlighter, because highlighting is
|
|
115
|
+
either a dependency or a hand-rolled tokeniser and both are the renderer
|
|
116
|
+
starting to interpret content it was handed literally.
|
|
117
|
+
|
|
118
|
+
**`flow`** — `title` and `steps`. Each step has `id`, `title`, and optionally
|
|
119
|
+
`detail`, `next`, `evidence`. The first step is the entry. `next` is optional
|
|
120
|
+
and defaults to the following step, which is what a linear flow means without
|
|
121
|
+
saying so. Every step must be reachable from the first, and the graph must be
|
|
122
|
+
acyclic.
|
|
123
|
+
|
|
124
|
+
**`quiz`** — `questions`, each with `id`, `prompt`, `options` (2–8, distinct),
|
|
125
|
+
`answer` (an index into that question's own options), and optionally
|
|
126
|
+
`explanation` and `evidence`.
|
|
127
|
+
|
|
128
|
+
**`exercise`** — `title`, `body`, and optionally `hints` and `evidence`.
|
|
129
|
+
|
|
130
|
+
## What a producer cannot assert
|
|
131
|
+
|
|
132
|
+
There is no field for whether the specification was validated, whether its
|
|
133
|
+
evidence checked out, or whether the artifact is trustworthy. Those are claims
|
|
134
|
+
about work the engine performs, and the engine is the only thing entitled to
|
|
135
|
+
make them — see the verification section of `validation.md`.
|
|
136
|
+
|
|
137
|
+
Nor is there a field for the *wording* of those claims. The three provenance
|
|
138
|
+
sentences are the renderer's, and a producer cannot select one, soften one, or
|
|
139
|
+
request one it has not earned. `provenance` is not an exception: it declares what
|
|
140
|
+
kind of claim the diagram is making and thereby what will be *required* of it. A
|
|
141
|
+
producer choosing `derived` is choosing the stricter rules, not choosing the
|
|
142
|
+
stronger sentence.
|
|
143
|
+
|
|
144
|
+
## Text is text
|
|
145
|
+
|
|
146
|
+
No field is Markdown and no field is HTML. Every producer string is escaped on
|
|
147
|
+
the way out, so `<b>bold</b>` in a title renders as those nine characters,
|
|
148
|
+
visibly. That is the boundary working: content that smuggles markup is a
|
|
149
|
+
producer reaching for presentation by another route.
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Validation and delivery
|
|
2
|
+
|
|
3
|
+
Four layers. Each supports a different claim, each is reported separately, and
|
|
4
|
+
none of them is a warning.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
structural the specification satisfies its schema
|
|
8
|
+
composition identifiers, references, graphs and answers are coherent
|
|
9
|
+
evidence every citation resolves at the declared commit
|
|
10
|
+
delivery the artifact was rendered, digested, and committed atomically
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Structural runs first and alone: composition and evidence assume a specification
|
|
14
|
+
that already has the right shape, so if structural fails they do not run and the
|
|
15
|
+
report says so rather than showing them as passing. Composition and evidence are
|
|
16
|
+
independent of each other and run together, so one round trip surfaces both.
|
|
17
|
+
|
|
18
|
+
A layer can also be **not run because it does not apply**, which is reported
|
|
19
|
+
distinctly from both a pass and a skip. A `diagram` declaring no source has no
|
|
20
|
+
evidence layer: there is no commit to resolve against, and the structural layer
|
|
21
|
+
has already refused any citation in it, so there is provably nothing to check.
|
|
22
|
+
The report says `~ evidence: not run` and gives the reason.
|
|
23
|
+
|
|
24
|
+
That distinction is the whole point of keeping it. Reporting such a layer as
|
|
25
|
+
`ok evidence` would put the strongest word in the report against the weakest
|
|
26
|
+
claim in it, and a reader skimming four green lines would conclude the citations
|
|
27
|
+
had been verified when there were none.
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
node engine/bin/render.mjs validate <spec.json> [--repo <dir>] [--json]
|
|
33
|
+
node engine/bin/render.mjs deliver <spec.json> <out.html> [--repo <dir>] [--json]
|
|
34
|
+
node engine/bin/render.mjs doctor [--json]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`--repo` names the repository whose history evidence resolves in. It defaults to
|
|
38
|
+
the specification's own directory, which is right when the specification lives
|
|
39
|
+
in the repository it cites, and is exactly what needs overriding when it does
|
|
40
|
+
not.
|
|
41
|
+
|
|
42
|
+
Exit codes are the contract:
|
|
43
|
+
|
|
44
|
+
| Code | Meaning |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| 0 | every layer that ran passed, and the artifact was committed |
|
|
47
|
+
| 1 | a layer failed; nothing was committed |
|
|
48
|
+
| 2 | the command line was wrong |
|
|
49
|
+
|
|
50
|
+
A non-zero exit is never reported as success.
|
|
51
|
+
|
|
52
|
+
## Structural
|
|
53
|
+
|
|
54
|
+
Schema validation, plus two refusals that come before it:
|
|
55
|
+
|
|
56
|
+
| Code | Meaning |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `schema_version_unsupported` | the specification is written against a contract this engine does not implement |
|
|
59
|
+
| `kind_unsupported` | no such artifact kind. Refused — not a reason to improvise HTML |
|
|
60
|
+
| `presentation_control` | a field like `color`, `css`, `class`, `layout` or `theme`. Rejected, never ignored |
|
|
61
|
+
| `unknown_field` | a field this contract does not have |
|
|
62
|
+
| `missing_field` | a required field is absent |
|
|
63
|
+
| `schema_*` | one keyword rejected one value; the message names both |
|
|
64
|
+
| `topology_unsupported` | a `diagram` asked for a layout this renderer does not have |
|
|
65
|
+
| `label_too_long` | a label is wider than its column cap. Refused, never shortened |
|
|
66
|
+
| `source_required_for_derived` | a `derived` diagram does not say which repository or which commit it was derived from |
|
|
67
|
+
| `citation_without_source` | a specification with no source carries a citation. There is nothing to resolve it against |
|
|
68
|
+
| `artifact_summary_forbidden` | a `derived` diagram carries `artifact.summary`, which is the one claim-bearing prose with nowhere to put a citation |
|
|
69
|
+
|
|
70
|
+
The last three are the provenance contract's structural half — see
|
|
71
|
+
**Provenance** below.
|
|
72
|
+
|
|
73
|
+
`presentation_control` and `unknown_field` have the same *outcome* —
|
|
74
|
+
`additionalProperties: false` rejects either. They differ in the diagnostic,
|
|
75
|
+
because a producer who wrote `"color"` believed presentation was theirs to set,
|
|
76
|
+
and the error should say so instead of talking about arrays and properties.
|
|
77
|
+
|
|
78
|
+
## Composition
|
|
79
|
+
|
|
80
|
+
| Code | Meaning |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `duplicate_identifier` | two modules or sections share an id; identifiers become link targets |
|
|
83
|
+
| `duplicate_step_identifier` | two steps of one flow share an id |
|
|
84
|
+
| `duplicate_question_identifier` | two questions of one quiz share an id |
|
|
85
|
+
| `unresolved_reference` | a `requires` or `next` names something that does not exist |
|
|
86
|
+
| `graph_cycle` | a module or flow graph leads back to itself |
|
|
87
|
+
| `orphan_step` | a flow step is unreachable from the flow's first step |
|
|
88
|
+
| `orphan_module` | a module is unreachable from every module without prerequisites |
|
|
89
|
+
| `answer_out_of_range` | a quiz answer does not index its own options |
|
|
90
|
+
| `module_empty` | a module carries no sections |
|
|
91
|
+
|
|
92
|
+
Note what "orphan" means for modules. With no `requires` anywhere, every module
|
|
93
|
+
is a root and nothing is orphaned — a flat list is a legal graph, and that is
|
|
94
|
+
the single-module case, which must stay legal. Orphans only become possible once
|
|
95
|
+
edges exist.
|
|
96
|
+
|
|
97
|
+
## Evidence
|
|
98
|
+
|
|
99
|
+
Resolved against the commit, using local Git. Nothing is fetched, cloned, or
|
|
100
|
+
looked up over a network: a commit that is not present locally is a commit whose
|
|
101
|
+
evidence was not verified, and the engine says so rather than going to find one.
|
|
102
|
+
|
|
103
|
+
| Code | Meaning |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `source_commit_unavailable` | the declared commit cannot be resolved here — wrong repository, unfetched commit, or no repository at all |
|
|
106
|
+
| `evidence_path_absent` | the commit resolves; the file is not in it |
|
|
107
|
+
| `evidence_range_invalid` | the file is there; the cited lines are not |
|
|
108
|
+
| `concept_without_evidence` | a concept cites nothing |
|
|
109
|
+
| `node_without_evidence` | a `derived` diagram says a component exists and cites nothing for it |
|
|
110
|
+
| `edge_without_evidence` | a `derived` diagram asserts a relationship and cites nothing for it |
|
|
111
|
+
| `claim_without_evidence` | a `derived` group, path or view carries `summary` or `note` prose with no citation |
|
|
112
|
+
|
|
113
|
+
The first three stay apart because they mean three different things to the
|
|
114
|
+
person reading them: a stale fixture, a moved file, and a shifted range are
|
|
115
|
+
three different fixes.
|
|
116
|
+
|
|
117
|
+
None of them downgrades to a skip. An engine that shrugged at an unresolvable
|
|
118
|
+
commit would deliver an artifact whose provenance block claims its evidence was
|
|
119
|
+
verified when nothing was.
|
|
120
|
+
|
|
121
|
+
### What this layer establishes, and what it does not
|
|
122
|
+
|
|
123
|
+
That the cited file exists at the declared commit, that the cited line range
|
|
124
|
+
exists in it, and that the material is there to be read.
|
|
125
|
+
|
|
126
|
+
Not that the claim resting on it is true.
|
|
127
|
+
|
|
128
|
+
Repository documentation — a README, an ADR, a runbook — is first-class citation
|
|
129
|
+
material and is checked exactly the same way. That is not a statement that
|
|
130
|
+
documentation carries the same authority as the code it describes; it is a
|
|
131
|
+
statement that the engine can tell you where to look and cannot tell you whether
|
|
132
|
+
what you find is right.
|
|
133
|
+
|
|
134
|
+
**Pathfinder verifies provenance, not truth.** No diagnostic and no word in any
|
|
135
|
+
artifact may imply otherwise.
|
|
136
|
+
|
|
137
|
+
## Provenance
|
|
138
|
+
|
|
139
|
+
A `diagram` declares `provenance`, and it decides how strictly the diagram is
|
|
140
|
+
checked. There is no default: a diagram that did not say would have a trust level
|
|
141
|
+
assigned to it by the engine, and the engine has no business guessing which claim
|
|
142
|
+
its producer meant to make.
|
|
143
|
+
|
|
144
|
+
| | `derived` | `proposed`, with source | `proposed`, no source |
|
|
145
|
+
| --- | --- | --- | --- |
|
|
146
|
+
| `source` | required | permitted | absent |
|
|
147
|
+
| citations | required on every node and edge | optional | **refused** |
|
|
148
|
+
| claim-bearing group, path or view | must cite | optional | **refused** |
|
|
149
|
+
| label-only group, path or view | needs nothing | needs nothing | needs nothing |
|
|
150
|
+
| `artifact.summary` | refused | permitted | permitted |
|
|
151
|
+
| evidence layer | runs | runs | not run, with the reason |
|
|
152
|
+
|
|
153
|
+
A `derived` diagram maps what the repository asserts about itself at the declared
|
|
154
|
+
commit, so there is no uncited derived fact. A node says a component exists and
|
|
155
|
+
an edge says two things relate; each is a claim a reader must be able to go and
|
|
156
|
+
check. Evidence is *where a reader goes to check an assertion*, which is not
|
|
157
|
+
always the code implementing it — an actor is cited by the entry point that
|
|
158
|
+
accepts it, an external system by its client or its configuration, a subsystem by
|
|
159
|
+
its manifest or entry module.
|
|
160
|
+
|
|
161
|
+
A component that appears nowhere in source, configuration, infrastructure or
|
|
162
|
+
repository documentation is not eligible for a `derived` diagram at all. A
|
|
163
|
+
diagram that needs it is `proposed`.
|
|
164
|
+
|
|
165
|
+
A group, path or view is the one narrower case. Its label is a name, and a name
|
|
166
|
+
asserts nothing its already-cited members do not. Its `summary` or `note` is
|
|
167
|
+
prose making a further claim, and that is what needs backing.
|
|
168
|
+
|
|
169
|
+
`lesson` has no `provenance` field and is unaffected by any of it. `source`
|
|
170
|
+
remains unconditionally required there: the conditional lives in the diagram
|
|
171
|
+
schema, not in the shared contract, precisely so that making one kind's source
|
|
172
|
+
optional did not make every kind's provenance optional.
|
|
173
|
+
|
|
174
|
+
## Delivery
|
|
175
|
+
|
|
176
|
+
Delivery takes the specification as **bytes** and nothing else. It copies them,
|
|
177
|
+
parses that copy, validates the parsed value, renders that same value, and
|
|
178
|
+
reports the digest of those same bytes. There is no parameter through which a
|
|
179
|
+
caller could supply a specification object alongside unrelated bytes, because a
|
|
180
|
+
receipt that described a specification nobody rendered would be undetectable
|
|
181
|
+
downstream.
|
|
182
|
+
|
|
183
|
+
Rendering then runs against a deep-frozen copy, so a renderer that mutated its
|
|
184
|
+
own input would throw rather than quietly produce output nobody can reproduce
|
|
185
|
+
from the file on disk.
|
|
186
|
+
|
|
187
|
+
### Verification is earned, not asserted
|
|
188
|
+
|
|
189
|
+
An artifact carries a sentence saying its evidence was checked against the named
|
|
190
|
+
commit. It carries that sentence only when **both** of these are true:
|
|
191
|
+
|
|
192
|
+
1. this engine validated the specification and the validation passed, and
|
|
193
|
+
2. at least one citation actually resolved.
|
|
194
|
+
|
|
195
|
+
The second condition is the guard against a vacuous claim. A specification
|
|
196
|
+
carrying no citations passes the evidence layer by having nothing to fail, and an
|
|
197
|
+
artifact saying "every citation above was verified against the commit named here"
|
|
198
|
+
on the strength of that would be a stronger statement than anybody made — with
|
|
199
|
+
no citation above, and sometimes a commit that does not exist. Nothing to check
|
|
200
|
+
is not the same as everything checking out.
|
|
201
|
+
|
|
202
|
+
So a `proposed` diagram with a source and no citations gets no sentence, and
|
|
203
|
+
neither does a lesson that cites nothing. Both are still valid, and both still
|
|
204
|
+
deliver; what they do not get is credit for a check that had no subject.
|
|
205
|
+
|
|
206
|
+
#### The three wordings
|
|
207
|
+
|
|
208
|
+
Renderer-owned, and different in each state because each supports a different
|
|
209
|
+
claim. **No field in any specification selects, softens, or requests one.** They
|
|
210
|
+
live in the shared shell rather than in a kind renderer because they describe
|
|
211
|
+
what the *engine* did, which is the same whatever kind rendered into it.
|
|
212
|
+
|
|
213
|
+
| State | What the artifact says |
|
|
214
|
+
| --- | --- |
|
|
215
|
+
| `derived` | the schema, references and graphs checked out, and every component, relationship and claim carried a citation verified at the declared commit. Explicitly **not** a finding that the architecture drawn is correct or complete |
|
|
216
|
+
| `proposed`, source, ≥1 resolved citation | the citations supplied were verified at the declared commit, and explicitly **do not** establish that the system drawn exists |
|
|
217
|
+
| `proposed`, source, no citations | nothing |
|
|
218
|
+
| `proposed`, no source | no verification sentence. Instead: that the diagram describes an intended system, names no repository or commit, and makes no claim about what currently exists |
|
|
219
|
+
| `lesson` | unchanged from what it has always said |
|
|
220
|
+
|
|
221
|
+
A source-less artifact also carries **no repository row, no commit row and no
|
|
222
|
+
timestamp**. None of them is invented from the working directory, the
|
|
223
|
+
environment, or a clock: there is no honest value, and a plausible one would be
|
|
224
|
+
worse than none, because it would read as provenance.
|
|
225
|
+
|
|
226
|
+
`render()` is a public export and does not validate. Called directly it emits
|
|
227
|
+
the provenance rows and **no verification language at all** — not "unverified",
|
|
228
|
+
not a placeholder, nothing. An artifact that cannot vouch for itself says
|
|
229
|
+
nothing on the subject, and the absence of the sentence is the signal; saying
|
|
230
|
+
"unverified" would still be the renderer making a claim about a process it did
|
|
231
|
+
not observe.
|
|
232
|
+
|
|
233
|
+
The claim is gated on an attestation, which only `verification.attest()` mints,
|
|
234
|
+
and only from a validation result this engine branded on the way past. The brand
|
|
235
|
+
is a module-private symbol — not `Symbol.for`, not a string key — so a
|
|
236
|
+
hand-built `{ ok: true }` is refused. `deliver()` validates and mints one
|
|
237
|
+
itself.
|
|
238
|
+
|
|
239
|
+
There is no producer-facing counterpart, and there must never be one. No
|
|
240
|
+
`verified`, no `validation_status`, no field of any name in the specification
|
|
241
|
+
can influence this. A producer asserting that its own work was checked is
|
|
242
|
+
exactly the claim this design exists to make impossible.
|
|
243
|
+
|
|
244
|
+
The artifact is written to a temporary file beside the destination, flushed with
|
|
245
|
+
`fsync`, and renamed over it. A rename within a directory is atomic, so a reader
|
|
246
|
+
never sees a half-written page. The `fsync` matters as much as the rename:
|
|
247
|
+
without it the rename can be durable while the content behind it is not, which
|
|
248
|
+
is how a crash leaves a correctly named, empty artifact.
|
|
249
|
+
|
|
250
|
+
A failure at any point leaves a previously delivered artifact exactly as it was.
|
|
251
|
+
|
|
252
|
+
| Code | Meaning |
|
|
253
|
+
| --- | --- |
|
|
254
|
+
| `render_failed` | rendering threw; nothing was written |
|
|
255
|
+
| `carriage_return_in_output` | output contains `\r`; artifacts use `\n` only |
|
|
256
|
+
| `byte_order_mark_in_output` | output begins with a BOM; artifacts are UTF-8 without one |
|
|
257
|
+
| `commit_failed` | the artifact could not be written or renamed |
|
|
258
|
+
|
|
259
|
+
The receipt names the renderer version and the SHA-256 and byte count of both
|
|
260
|
+
the specification and the artifact.
|
|
261
|
+
|
|
262
|
+
## What a green result is not
|
|
263
|
+
|
|
264
|
+
It is not a judgement that the artifact looks right. Nobody has looked at it.
|
|
265
|
+
|
|
266
|
+
Report the two separately, always. A human opening the artifact in a browser is
|
|
267
|
+
a different kind of evidence about a different question, and neither result
|
|
268
|
+
supports the other.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The artifact's inline behaviour: theme choice, navigation position, and quiz
|
|
3
|
+
* feedback.
|
|
4
|
+
*
|
|
5
|
+
* All three are enhancements. With scripting off the artifact is still a
|
|
6
|
+
* complete, readable, keyboard-navigable document: the theme follows the
|
|
7
|
+
* reader's system through `prefers-color-scheme`, navigation is a list of
|
|
8
|
+
* ordinary anchors, and every quiz answer is already reachable inside a
|
|
9
|
+
* `<details>`. Nothing here is load-bearing, which is what lets the artifact
|
|
10
|
+
* open from `file://` with no network, no server, and no build step.
|
|
11
|
+
*
|
|
12
|
+
* Storage is wrapped: `localStorage` throws outright in some `file://` and
|
|
13
|
+
* private-window configurations, and a theme preference is not worth a page
|
|
14
|
+
* that fails to run.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const BEHAVIOR_JS = `
|
|
18
|
+
(function () {
|
|
19
|
+
"use strict";
|
|
20
|
+
|
|
21
|
+
var root = document.documentElement;
|
|
22
|
+
var MODES = ["auto", "light", "dark"];
|
|
23
|
+
var LABELS = { auto: "Theme: auto", light: "Theme: light", dark: "Theme: dark" };
|
|
24
|
+
var KEY = "pathfinder.artifact.theme";
|
|
25
|
+
|
|
26
|
+
function stored() {
|
|
27
|
+
try {
|
|
28
|
+
var value = window.localStorage.getItem(KEY);
|
|
29
|
+
return MODES.indexOf(value) >= 0 ? value : null;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function remember(mode) {
|
|
36
|
+
try {
|
|
37
|
+
window.localStorage.setItem(KEY, mode);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
/* A reader who cannot store a preference still gets to use it. */
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function apply(mode, button) {
|
|
44
|
+
root.setAttribute("data-pf-theme", mode);
|
|
45
|
+
if (button) {
|
|
46
|
+
button.textContent = LABELS[mode];
|
|
47
|
+
button.setAttribute("aria-label", LABELS[mode] + ". Activate to change.");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
var toggle = document.getElementById("pf-theme-toggle");
|
|
52
|
+
var initial = stored() || "auto";
|
|
53
|
+
apply(initial, toggle);
|
|
54
|
+
|
|
55
|
+
if (toggle) {
|
|
56
|
+
toggle.hidden = false;
|
|
57
|
+
toggle.addEventListener("click", function () {
|
|
58
|
+
var next = MODES[(MODES.indexOf(root.getAttribute("data-pf-theme")) + 1) % MODES.length];
|
|
59
|
+
apply(next, toggle);
|
|
60
|
+
remember(next);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Mark the module the reader is in. Falls back to doing nothing where
|
|
65
|
+
IntersectionObserver is unavailable; the nav still navigates. */
|
|
66
|
+
var links = Array.prototype.slice.call(document.querySelectorAll("[data-pf-nav]"));
|
|
67
|
+
if (links.length > 0 && "IntersectionObserver" in window) {
|
|
68
|
+
var byId = {};
|
|
69
|
+
links.forEach(function (link) { byId[link.getAttribute("data-pf-nav")] = link; });
|
|
70
|
+
|
|
71
|
+
var visible = {};
|
|
72
|
+
var observer = new IntersectionObserver(function (entries) {
|
|
73
|
+
entries.forEach(function (entry) {
|
|
74
|
+
visible[entry.target.id] = entry.isIntersecting;
|
|
75
|
+
});
|
|
76
|
+
var current = null;
|
|
77
|
+
for (var i = 0; i < links.length; i += 1) {
|
|
78
|
+
var id = links[i].getAttribute("data-pf-nav");
|
|
79
|
+
if (visible[id]) { current = id; break; }
|
|
80
|
+
}
|
|
81
|
+
links.forEach(function (link) {
|
|
82
|
+
var isCurrent = link.getAttribute("data-pf-nav") === current;
|
|
83
|
+
if (isCurrent) {
|
|
84
|
+
link.setAttribute("aria-current", "true");
|
|
85
|
+
} else {
|
|
86
|
+
link.removeAttribute("aria-current");
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}, { rootMargin: "-30% 0px -60% 0px" });
|
|
90
|
+
|
|
91
|
+
Object.keys(byId).forEach(function (id) {
|
|
92
|
+
var target = document.getElementById(id);
|
|
93
|
+
if (target) observer.observe(target);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Quiz feedback. The correct answer is in the document either way; choosing
|
|
98
|
+
an option just says so sooner, and opens the explanation that was always
|
|
99
|
+
one click away. */
|
|
100
|
+
document.addEventListener("change", function (event) {
|
|
101
|
+
var input = event.target;
|
|
102
|
+
if (!input || input.type !== "radio" || !input.hasAttribute("data-pf-answer")) return;
|
|
103
|
+
|
|
104
|
+
var question = input.closest("[data-pf-question]");
|
|
105
|
+
if (!question) return;
|
|
106
|
+
|
|
107
|
+
var chosenIsCorrect = input.getAttribute("data-pf-answer") === "correct";
|
|
108
|
+
Array.prototype.forEach.call(question.querySelectorAll(".pf-option"), function (option) {
|
|
109
|
+
var field = option.querySelector("input[type=radio]");
|
|
110
|
+
var verdict = option.querySelector(".pf-verdict");
|
|
111
|
+
var correct = field.getAttribute("data-pf-answer") === "correct";
|
|
112
|
+
if (field.checked && !correct) {
|
|
113
|
+
option.setAttribute("data-pf-verdict", "incorrect");
|
|
114
|
+
if (verdict) verdict.textContent = "Not this one";
|
|
115
|
+
} else if (correct && (field.checked || !chosenIsCorrect)) {
|
|
116
|
+
option.setAttribute("data-pf-verdict", "correct");
|
|
117
|
+
if (verdict) verdict.textContent = "Correct";
|
|
118
|
+
} else {
|
|
119
|
+
option.removeAttribute("data-pf-verdict");
|
|
120
|
+
if (verdict) verdict.textContent = "";
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
var reveal = question.querySelector(".pf-reveal");
|
|
125
|
+
if (reveal) reveal.open = true;
|
|
126
|
+
});
|
|
127
|
+
})();
|
|
128
|
+
`.trim();
|