@brandon_m_behring/book-scaffold-astro 4.7.0 → 4.8.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/components/Provenance.astro +206 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +27 -4
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.mjs +24 -4
- package/package.json +2 -1
- package/pages/chapters/[...slug].astro +9 -0
- package/recipes/04-component-library.md +18 -0
- package/src/schemas.ts +49 -0
package/CLAUDE.md
CHANGED
|
@@ -99,6 +99,8 @@ Two callout families coexist. Authors import what they need.
|
|
|
99
99
|
|
|
100
100
|
**Utility components** (`src/components/`, any profile): `Cite`, `XRef`, `Figure`, `MarginNote`, `Sidenote`, `WeekRef`, `CodeRef`, `CodeBlock`, `Tag`, `StatusBadge`, `PocLayout` (v4.1.0+; wraps slot in a per-`kind` layout shell — 5 closed-union kinds; see `recipes/15-defining-styles.md`).
|
|
101
101
|
|
|
102
|
+
**Provenance** (v4.8.0, any profile, **auto-injected by the chapter route — not author-imported**): per-chapter "How this was made" audit-trail block, rendered from the optional `provenance` frontmatter (`ai_tools`, `prompts_archive`, `decisions_log`, `audit_history`, `citation_backstop`). **Opt-out**: a chapter with no `provenance` shows a fallback ("Audit history not yet recorded"). Distinct from `AICollaborationDisclosure` (book-level, manual model+role disclosure). Repo-relative path fields render as `<code>`; only `http(s)` values link.
|
|
103
|
+
|
|
102
104
|
Full reference in `recipes/04-component-library.md`.
|
|
103
105
|
|
|
104
106
|
## Citation patterns
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Provenance — per-chapter "How this was made" audit-trail disclosure (v4.8.0).
|
|
4
|
+
*
|
|
5
|
+
* Renders the optional `provenance` chapter frontmatter (see provenanceObject in
|
|
6
|
+
* src/schemas.ts) as a collapsed <details> block, family-styled with the warm-plum
|
|
7
|
+
* disclosure language (cf. AICollaborationDisclosure.astro) and the ▸/▾ summary
|
|
8
|
+
* marker (cf. .chapter-toc in styles/chapter.css). No JavaScript.
|
|
9
|
+
*
|
|
10
|
+
* Auto-injected by the chapter route (pages/chapters/[...slug].astro) on EVERY
|
|
11
|
+
* chapter of EVERY profile — so it is OPT-OUT, not opt-in: a chapter with no
|
|
12
|
+
* `provenance` still renders, showing a fallback ("Audit history not yet
|
|
13
|
+
* recorded — initial draft"). This forces awareness and signals to readers that
|
|
14
|
+
* an audit trail is expected.
|
|
15
|
+
*
|
|
16
|
+
* Scope (deliberate): this block owns the PROCESS audit trail (ai_tools, prompts,
|
|
17
|
+
* decisions, audits, citation backstop). It does NOT render freshness/volatility —
|
|
18
|
+
* ChapterHeader.astro owns that "is this current?" badge. And it is distinct from
|
|
19
|
+
* AICollaborationDisclosure (book-level, manual model+role disclosure).
|
|
20
|
+
*
|
|
21
|
+
* Claim-safety: references are repo-relative paths (e.g. "audits/AUDIT_x.md",
|
|
22
|
+
* "DECISIONS.md#anchor") that do NOT resolve on the built static site, so they
|
|
23
|
+
* render as <code> text. Only genuine http(s) URLs become clickable links — the
|
|
24
|
+
* site's "no dead links" thesis applied to its own chrome.
|
|
25
|
+
*/
|
|
26
|
+
import type { Provenance } from '../src/schemas.js';
|
|
27
|
+
|
|
28
|
+
interface Props {
|
|
29
|
+
data: Provenance | null;
|
|
30
|
+
}
|
|
31
|
+
const { data } = Astro.props;
|
|
32
|
+
|
|
33
|
+
const isUrl = (s: string) => /^https?:\/\//i.test(s);
|
|
34
|
+
const fmtDate = (d: Date | string) =>
|
|
35
|
+
(d instanceof Date ? d : new Date(d)).toISOString().slice(0, 10);
|
|
36
|
+
|
|
37
|
+
const aiTools = data?.ai_tools ?? [];
|
|
38
|
+
const audits = data?.audit_history ?? [];
|
|
39
|
+
const backstop = data?.citation_backstop;
|
|
40
|
+
const promptsArchive = data?.prompts_archive;
|
|
41
|
+
const decisionsLog = data?.decisions_log;
|
|
42
|
+
|
|
43
|
+
// Opt-out (D3): the block always renders; "empty" → fallback.
|
|
44
|
+
const hasContent =
|
|
45
|
+
aiTools.length > 0 ||
|
|
46
|
+
audits.length > 0 ||
|
|
47
|
+
Boolean(backstop) ||
|
|
48
|
+
Boolean(promptsArchive) ||
|
|
49
|
+
Boolean(decisionsLog);
|
|
50
|
+
|
|
51
|
+
// Teaser digest shown in the <summary> so the signal reads while collapsed.
|
|
52
|
+
const teaserParts: string[] = [];
|
|
53
|
+
if (audits.length) teaserParts.push(`${audits.length} audit${audits.length > 1 ? 's' : ''}`);
|
|
54
|
+
if (backstop) teaserParts.push(`${backstop}-backed`);
|
|
55
|
+
if (!teaserParts.length && aiTools.length) {
|
|
56
|
+
teaserParts.push(`${aiTools.length} tool${aiTools.length > 1 ? 's' : ''}`);
|
|
57
|
+
}
|
|
58
|
+
const teaser = teaserParts.length ? `How this was made · ${teaserParts.join(' · ')}` : 'How this was made';
|
|
59
|
+
---
|
|
60
|
+
<aside class="provenance" role="note" aria-labelledby="provenance-h">
|
|
61
|
+
<details class="provenance-details">
|
|
62
|
+
<summary id="provenance-h">{teaser}</summary>
|
|
63
|
+
|
|
64
|
+
{hasContent ? (
|
|
65
|
+
<div class="provenance-body">
|
|
66
|
+
{aiTools.length > 0 && (
|
|
67
|
+
<p class="provenance-row">
|
|
68
|
+
<span class="provenance-label">AI tools</span>
|
|
69
|
+
{aiTools.map((t) => <span class="provenance-chip">{t}</span>)}
|
|
70
|
+
</p>
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
{(promptsArchive || decisionsLog) && (
|
|
74
|
+
<p class="provenance-row">
|
|
75
|
+
<span class="provenance-label">References</span>
|
|
76
|
+
{promptsArchive && (
|
|
77
|
+
<span class="provenance-ref">prompts: {isUrl(promptsArchive)
|
|
78
|
+
? <a href={promptsArchive}>{promptsArchive}</a>
|
|
79
|
+
: <code>{promptsArchive}</code>}</span>
|
|
80
|
+
)}
|
|
81
|
+
{decisionsLog && (
|
|
82
|
+
<span class="provenance-ref">decisions: {isUrl(decisionsLog)
|
|
83
|
+
? <a href={decisionsLog}>{decisionsLog}</a>
|
|
84
|
+
: <code>{decisionsLog}</code>}</span>
|
|
85
|
+
)}
|
|
86
|
+
</p>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
{audits.length > 0 && (
|
|
90
|
+
<div class="provenance-row">
|
|
91
|
+
<span class="provenance-label">Audit history</span>
|
|
92
|
+
<ul class="provenance-audits">
|
|
93
|
+
{audits.map((a) => (
|
|
94
|
+
<li>
|
|
95
|
+
<time datetime={fmtDate(a.date)}>{fmtDate(a.date)}</time>
|
|
96
|
+
<span class="provenance-audit-type">{a.type}</span>
|
|
97
|
+
{isUrl(a.file) ? <a href={a.file}>{a.file}</a> : <code>{a.file}</code>}
|
|
98
|
+
</li>
|
|
99
|
+
))}
|
|
100
|
+
</ul>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
|
|
104
|
+
{backstop && (
|
|
105
|
+
<p class="provenance-row">
|
|
106
|
+
<span class="provenance-label">Citation backstop</span>
|
|
107
|
+
<span class={`provenance-badge backstop-${backstop}`}>{backstop}</span>
|
|
108
|
+
</p>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
) : (
|
|
112
|
+
<p class="provenance-fallback">
|
|
113
|
+
Audit history not yet recorded — initial draft.
|
|
114
|
+
</p>
|
|
115
|
+
)}
|
|
116
|
+
</details>
|
|
117
|
+
</aside>
|
|
118
|
+
|
|
119
|
+
<style>
|
|
120
|
+
.provenance {
|
|
121
|
+
display: block;
|
|
122
|
+
margin: var(--space-6) 0;
|
|
123
|
+
padding: var(--space-3) var(--space-4);
|
|
124
|
+
border-left: var(--border-bar) solid var(--warm-plum);
|
|
125
|
+
background: var(--warm-plum-tint);
|
|
126
|
+
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
|
127
|
+
font-size: var(--text-sm);
|
|
128
|
+
line-height: var(--leading-normal);
|
|
129
|
+
max-width: var(--measure-main);
|
|
130
|
+
}
|
|
131
|
+
.provenance-details > summary {
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
font-weight: 500;
|
|
134
|
+
color: var(--warm-plum);
|
|
135
|
+
letter-spacing: 0.02em;
|
|
136
|
+
list-style: none;
|
|
137
|
+
}
|
|
138
|
+
.provenance-details > summary::-webkit-details-marker {
|
|
139
|
+
display: none;
|
|
140
|
+
}
|
|
141
|
+
.provenance-details > summary::before {
|
|
142
|
+
content: '▸ ';
|
|
143
|
+
display: inline-block;
|
|
144
|
+
}
|
|
145
|
+
.provenance-details[open] > summary::before {
|
|
146
|
+
content: '▾ ';
|
|
147
|
+
}
|
|
148
|
+
.provenance-body {
|
|
149
|
+
margin-top: var(--space-3);
|
|
150
|
+
}
|
|
151
|
+
.provenance-row {
|
|
152
|
+
margin: var(--space-2) 0;
|
|
153
|
+
text-indent: 0;
|
|
154
|
+
}
|
|
155
|
+
.provenance-label {
|
|
156
|
+
display: inline-block;
|
|
157
|
+
min-width: 9rem;
|
|
158
|
+
font-size: var(--text-xs);
|
|
159
|
+
text-transform: uppercase;
|
|
160
|
+
letter-spacing: 0.04em;
|
|
161
|
+
color: var(--warm-plum);
|
|
162
|
+
vertical-align: top;
|
|
163
|
+
}
|
|
164
|
+
.provenance-chip,
|
|
165
|
+
.provenance-badge {
|
|
166
|
+
display: inline-block;
|
|
167
|
+
margin: 0 var(--space-1) var(--space-1) 0;
|
|
168
|
+
padding: 0.1em 0.5em;
|
|
169
|
+
border-radius: var(--radius-sm);
|
|
170
|
+
background: color-mix(in srgb, var(--warm-plum) 12%, var(--paper));
|
|
171
|
+
font-size: var(--text-xs);
|
|
172
|
+
}
|
|
173
|
+
.provenance-ref {
|
|
174
|
+
display: inline-block;
|
|
175
|
+
margin-right: var(--space-3);
|
|
176
|
+
}
|
|
177
|
+
.provenance-ref code,
|
|
178
|
+
.provenance-audits code {
|
|
179
|
+
font-family: var(--font-code);
|
|
180
|
+
font-size: 0.95em;
|
|
181
|
+
background: var(--color-code-bg);
|
|
182
|
+
padding: 0.1em 0.4em;
|
|
183
|
+
border-radius: var(--radius-sm);
|
|
184
|
+
}
|
|
185
|
+
.provenance-audits {
|
|
186
|
+
list-style: none;
|
|
187
|
+
margin: var(--space-1) 0 0;
|
|
188
|
+
padding-left: 0;
|
|
189
|
+
}
|
|
190
|
+
.provenance-audits li {
|
|
191
|
+
margin: var(--space-1) 0;
|
|
192
|
+
}
|
|
193
|
+
.provenance-audits time {
|
|
194
|
+
font-family: var(--font-code);
|
|
195
|
+
margin-right: var(--space-2);
|
|
196
|
+
}
|
|
197
|
+
.provenance-audit-type {
|
|
198
|
+
margin-right: var(--space-2);
|
|
199
|
+
font-style: italic;
|
|
200
|
+
}
|
|
201
|
+
.provenance-fallback {
|
|
202
|
+
margin: var(--space-3) 0 0;
|
|
203
|
+
font-style: italic;
|
|
204
|
+
opacity: 0.8;
|
|
205
|
+
}
|
|
206
|
+
</style>
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AstroUserConfig, AstroIntegration } from 'astro';
|
|
2
|
-
import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions,
|
|
3
|
-
export { A as AcademicChapter, B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, i as CourseNotesChapter, F as FreshnessAffordance, j as FrontmatterRouteConfig, M as MinimalChapter, P as PartKey, k as PartialRouteToggles, l as ProfileDefinition, R as ResearchPortfolioChapter,
|
|
2
|
+
import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, Y as volatilityLevels, h as ChaptersRenderer, o as Style } from './types-DTQ2l6B6.js';
|
|
3
|
+
export { A as AcademicChapter, B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, i as CourseNotesChapter, F as FreshnessAffordance, j as FrontmatterRouteConfig, M as MinimalChapter, P as PartKey, k as PartialRouteToggles, l as ProfileDefinition, m as Provenance, R as ResearchPortfolioChapter, n as RouteToggles, S as StatusBadge, p as StyleInput, T as ToolsChapter, V as VolatilityBadge, q as academicChapterSchema, r as academicParts, s as changeKinds, t as changelogSchema, u as chapterStatus, v as citationBackstops, w as composeStyles, x as courseNotesChapterSchema, y as defineProfile, z as defineStyle, D as minimalChapterSchema, E as normalizeFrontmatterConfig, G as patternCategories, H as patternsSchema, I as provenanceObject, J as provenanceSchema, K as researchPortfolioChapterSchema, L as resolvePreset, N as resolveProfile, O as sourceTiers, Q as sourceTiersResearch, U as sourcesSchema, W as toolSlugs, X as toolsChapterSchema } from './types-DTQ2l6B6.js';
|
|
4
4
|
import 'astro/zod';
|
|
5
5
|
|
|
6
6
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -168,6 +168,18 @@ var chapterStatus = [
|
|
|
168
168
|
"scaffolded",
|
|
169
169
|
"planned"
|
|
170
170
|
];
|
|
171
|
+
var citationBackstops = ["research-kb", "manual", "unverified"];
|
|
172
|
+
var provenanceObject = z.object({
|
|
173
|
+
ai_tools: z.array(z.string()).default([]),
|
|
174
|
+
prompts_archive: z.string().optional(),
|
|
175
|
+
decisions_log: z.string().optional(),
|
|
176
|
+
audit_history: z.array(z.object({ date: z.date(), type: z.string(), file: z.string() })).default([]),
|
|
177
|
+
citation_backstop: z.enum(citationBackstops).optional()
|
|
178
|
+
}).strict();
|
|
179
|
+
var provenanceSchema = provenanceObject.refine(
|
|
180
|
+
(p) => p.ai_tools.length > 0 || p.audit_history.length > 0 || Boolean(p.citation_backstop) || Boolean(p.prompts_archive) || Boolean(p.decisions_log),
|
|
181
|
+
{ message: "provenance is present but empty \u2014 omit the key, or set at least one field" }
|
|
182
|
+
).optional();
|
|
171
183
|
var academicChapterSchema = z.object({
|
|
172
184
|
week: z.number().int().min(1).max(99),
|
|
173
185
|
part: z.enum(academicParts),
|
|
@@ -185,7 +197,9 @@ var academicChapterSchema = z.object({
|
|
|
185
197
|
published: z.date().optional(),
|
|
186
198
|
updated: z.date().optional(),
|
|
187
199
|
tags: z.array(z.string()).default([]),
|
|
188
|
-
image: z.string().optional()
|
|
200
|
+
image: z.string().optional(),
|
|
201
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
202
|
+
provenance: provenanceSchema
|
|
189
203
|
});
|
|
190
204
|
var toolsChapterSchema = z.object({
|
|
191
205
|
title: z.string().min(1),
|
|
@@ -203,7 +217,9 @@ var toolsChapterSchema = z.object({
|
|
|
203
217
|
author: z.string().optional(),
|
|
204
218
|
published: z.date().optional(),
|
|
205
219
|
tags: z.array(z.string()).default([]),
|
|
206
|
-
image: z.string().optional()
|
|
220
|
+
image: z.string().optional(),
|
|
221
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
222
|
+
provenance: provenanceSchema
|
|
207
223
|
});
|
|
208
224
|
var minimalChapterSchema = toolsChapterSchema;
|
|
209
225
|
var sourceTiersResearch = ["T1", "T2", "T3", "T4"];
|
|
@@ -237,7 +253,9 @@ var courseNotesChapterSchema = z.object({
|
|
|
237
253
|
author: z.string().optional(),
|
|
238
254
|
published: z.date().optional(),
|
|
239
255
|
updated: z.date().optional(),
|
|
240
|
-
image: z.string().optional()
|
|
256
|
+
image: z.string().optional(),
|
|
257
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
258
|
+
provenance: provenanceSchema
|
|
241
259
|
});
|
|
242
260
|
var researchPortfolioChapterSchema = z.object({
|
|
243
261
|
// Identity
|
|
@@ -295,7 +313,9 @@ var researchPortfolioChapterSchema = z.object({
|
|
|
295
313
|
// `tags` + `updated` already existed; `author` + `published` + `image` are new.
|
|
296
314
|
author: z.string().optional(),
|
|
297
315
|
published: z.date().optional(),
|
|
298
|
-
image: z.string().optional()
|
|
316
|
+
image: z.string().optional(),
|
|
317
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
318
|
+
provenance: provenanceSchema
|
|
299
319
|
});
|
|
300
320
|
var sourcesSchema = z.object({
|
|
301
321
|
url: z.string().url(),
|
|
@@ -1262,6 +1282,7 @@ export {
|
|
|
1262
1282
|
changelogSchema,
|
|
1263
1283
|
chapterSortKey,
|
|
1264
1284
|
chapterStatus,
|
|
1285
|
+
citationBackstops,
|
|
1265
1286
|
composeStyles,
|
|
1266
1287
|
courseNotesChapterSchema,
|
|
1267
1288
|
courseNotesStyle,
|
|
@@ -1278,6 +1299,8 @@ export {
|
|
|
1278
1299
|
normalizeFrontmatterConfig,
|
|
1279
1300
|
patternCategories,
|
|
1280
1301
|
patternsSchema,
|
|
1302
|
+
provenanceObject,
|
|
1303
|
+
provenanceSchema,
|
|
1281
1304
|
researchPortfolioChapterSchema,
|
|
1282
1305
|
researchPortfolioStyle,
|
|
1283
1306
|
resolvePreset,
|
package/dist/schemas.d.ts
CHANGED
package/dist/schemas.mjs
CHANGED
|
@@ -52,6 +52,18 @@ var chapterStatus = [
|
|
|
52
52
|
"scaffolded",
|
|
53
53
|
"planned"
|
|
54
54
|
];
|
|
55
|
+
var citationBackstops = ["research-kb", "manual", "unverified"];
|
|
56
|
+
var provenanceObject = z.object({
|
|
57
|
+
ai_tools: z.array(z.string()).default([]),
|
|
58
|
+
prompts_archive: z.string().optional(),
|
|
59
|
+
decisions_log: z.string().optional(),
|
|
60
|
+
audit_history: z.array(z.object({ date: z.date(), type: z.string(), file: z.string() })).default([]),
|
|
61
|
+
citation_backstop: z.enum(citationBackstops).optional()
|
|
62
|
+
}).strict();
|
|
63
|
+
var provenanceSchema = provenanceObject.refine(
|
|
64
|
+
(p) => p.ai_tools.length > 0 || p.audit_history.length > 0 || Boolean(p.citation_backstop) || Boolean(p.prompts_archive) || Boolean(p.decisions_log),
|
|
65
|
+
{ message: "provenance is present but empty \u2014 omit the key, or set at least one field" }
|
|
66
|
+
).optional();
|
|
55
67
|
var academicChapterSchema = z.object({
|
|
56
68
|
week: z.number().int().min(1).max(99),
|
|
57
69
|
part: z.enum(academicParts),
|
|
@@ -69,7 +81,9 @@ var academicChapterSchema = z.object({
|
|
|
69
81
|
published: z.date().optional(),
|
|
70
82
|
updated: z.date().optional(),
|
|
71
83
|
tags: z.array(z.string()).default([]),
|
|
72
|
-
image: z.string().optional()
|
|
84
|
+
image: z.string().optional(),
|
|
85
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
86
|
+
provenance: provenanceSchema
|
|
73
87
|
});
|
|
74
88
|
var toolsChapterSchema = z.object({
|
|
75
89
|
title: z.string().min(1),
|
|
@@ -87,7 +101,9 @@ var toolsChapterSchema = z.object({
|
|
|
87
101
|
author: z.string().optional(),
|
|
88
102
|
published: z.date().optional(),
|
|
89
103
|
tags: z.array(z.string()).default([]),
|
|
90
|
-
image: z.string().optional()
|
|
104
|
+
image: z.string().optional(),
|
|
105
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
106
|
+
provenance: provenanceSchema
|
|
91
107
|
});
|
|
92
108
|
var minimalChapterSchema = toolsChapterSchema;
|
|
93
109
|
var sourceTiersResearch = ["T1", "T2", "T3", "T4"];
|
|
@@ -121,7 +137,9 @@ var courseNotesChapterSchema = z.object({
|
|
|
121
137
|
author: z.string().optional(),
|
|
122
138
|
published: z.date().optional(),
|
|
123
139
|
updated: z.date().optional(),
|
|
124
|
-
image: z.string().optional()
|
|
140
|
+
image: z.string().optional(),
|
|
141
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
142
|
+
provenance: provenanceSchema
|
|
125
143
|
});
|
|
126
144
|
var researchPortfolioChapterSchema = z.object({
|
|
127
145
|
// Identity
|
|
@@ -179,7 +197,9 @@ var researchPortfolioChapterSchema = z.object({
|
|
|
179
197
|
// `tags` + `updated` already existed; `author` + `published` + `image` are new.
|
|
180
198
|
author: z.string().optional(),
|
|
181
199
|
published: z.date().optional(),
|
|
182
|
-
image: z.string().optional()
|
|
200
|
+
image: z.string().optional(),
|
|
201
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
202
|
+
provenance: provenanceSchema
|
|
183
203
|
});
|
|
184
204
|
var sourcesSchema = z.object({
|
|
185
205
|
url: z.string().url(),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandon_m_behring/book-scaffold-astro",
|
|
3
3
|
"description": "Astro 6 + MDX toolkit for long-form technical books. Profile-aware (academic / tools / minimal); ships Tufte typography, KaTeX, BibTeX citations, Pagefind, Cloudflare Workers deploy. See PACKAGE_DESIGN.md for the API contract.",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.8.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Brandon Behring",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"./components/PolicyRef.astro": "./components/PolicyRef.astro",
|
|
73
73
|
"./components/Practice.astro": "./components/Practice.astro",
|
|
74
74
|
"./components/PreReleaseBanner.astro": "./components/PreReleaseBanner.astro",
|
|
75
|
+
"./components/Provenance.astro": "./components/Provenance.astro",
|
|
75
76
|
"./components/Recovery.astro": "./components/Recovery.astro",
|
|
76
77
|
"./components/ResultBox.astro": "./components/ResultBox.astro",
|
|
77
78
|
"./components/Sidebar.astro": "./components/Sidebar.astro",
|
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
* [...slug].astro for the canonical pattern).
|
|
18
18
|
*/
|
|
19
19
|
import { getCollection, render } from 'astro:content';
|
|
20
|
+
import type { Provenance } from '../../src/schemas.js';
|
|
20
21
|
import Chapter from '../../layouts/Chapter.astro';
|
|
21
22
|
import Base from '../../layouts/Base.astro';
|
|
23
|
+
import ProvenanceBlock from '../../components/Provenance.astro';
|
|
22
24
|
|
|
23
25
|
const BOOK_PRESET = import.meta.env.BOOK_PRESET ?? 'minimal';
|
|
24
26
|
const USE_CHAPTER_LAYOUT = ['academic', 'research-portfolio'].includes(BOOK_PRESET);
|
|
@@ -34,7 +36,14 @@ export async function getStaticPaths() {
|
|
|
34
36
|
const { entry } = Astro.props;
|
|
35
37
|
const { Content, headings } = await render(entry);
|
|
36
38
|
const Layout = USE_CHAPTER_LAYOUT ? Chapter : Base;
|
|
39
|
+
|
|
40
|
+
// v4.8.0: per-chapter provenance audit trail. Rendered here (not in a single
|
|
41
|
+
// layout) so it reaches BOTH layout paths — Chapter.astro (academic/
|
|
42
|
+
// research-portfolio) AND Base.astro (tools/minimal/course-notes). Opt-out:
|
|
43
|
+
// always rendered; absent `provenance` → the component's fallback.
|
|
44
|
+
const provenance = (entry.data as { provenance?: Provenance }).provenance ?? null;
|
|
37
45
|
---
|
|
38
46
|
<Layout entry={entry} headings={headings}>
|
|
39
47
|
<Content />
|
|
48
|
+
<ProvenanceBlock data={provenance} />
|
|
40
49
|
</Layout>
|
|
@@ -73,6 +73,24 @@ Supported `type` values: `theorem`, `proposition`, `lemma`, `corollary`, `defini
|
|
|
73
73
|
| `Tag` | Inline volatility/topic tag | `<Tag>stable-principle</Tag>` |
|
|
74
74
|
| `StatusBadge` | Render frontmatter `status` value with color | `<StatusBadge status={frontmatter.status} />` |
|
|
75
75
|
| `ChapterHeader` | Auto-rendered metadata block (week, part, status, companion links) | placed at top of each chapter automatically by Chapter.astro |
|
|
76
|
+
| `Provenance` | Auto-rendered per-chapter audit trail (v4.8.0) | placed at the end of each chapter by the chapter route — not imported |
|
|
77
|
+
|
|
78
|
+
## Per-chapter provenance (v4.8.0, auto-injected)
|
|
79
|
+
|
|
80
|
+
`Provenance` renders a collapsible "How this was made" block on **every** chapter — you don't import or place it. It reads the optional `provenance` frontmatter and is **opt-out**: a chapter with no `provenance` shows a fallback ("Audit history not yet recorded"). It surfaces *process* (the audit trail); `ChapterHeader` still owns *freshness*. Distinct from `AICollaborationDisclosure` (book-level, manual model+role disclosure).
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
provenance:
|
|
84
|
+
ai_tools: ['Claude Code (Opus 4.8)', 'research-kb']
|
|
85
|
+
prompts_archive: docs/sessions/2026-05-22--ch07.md # repo-relative path or URL
|
|
86
|
+
decisions_log: DECISIONS.md#ch07-derivation # repo-relative path or URL
|
|
87
|
+
audit_history:
|
|
88
|
+
- { date: 2026-05-15, type: routine, file: audits/AUDIT_2026-05-15.md }
|
|
89
|
+
- { date: 2026-05-22, type: independent, file: audits/AUDIT_2026-05-22.md }
|
|
90
|
+
citation_backstop: research-kb # research-kb | manual | unverified
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Repo-relative paths render as `<code>`; only `http(s)` values become links (no dead links). If present, the `provenance` object must be non-empty (omit the key to opt out — unknown keys are rejected). `citation_backstop` is a closed set; `audit_history[].type` is free text.
|
|
76
94
|
|
|
77
95
|
## Conditional imports
|
|
78
96
|
|
package/src/schemas.ts
CHANGED
|
@@ -70,6 +70,46 @@ export const chapterStatus = [
|
|
|
70
70
|
'planned',
|
|
71
71
|
] as const;
|
|
72
72
|
|
|
73
|
+
// ===== Provenance (v4.8.0) — process-as-artifact audit trail =====
|
|
74
|
+
//
|
|
75
|
+
// Optional per-chapter block attached to EVERY profile schema below.
|
|
76
|
+
// components/Provenance.astro renders it as a collapsible "How this was made"
|
|
77
|
+
// disclosure (opt-out: absent → fallback). Distinct from AICollaborationDisclosure
|
|
78
|
+
// (book-level, manual). Paths are repo-relative, so prompts_archive / decisions_log
|
|
79
|
+
// use plain z.string() — NOT .url() (which would reject "DECISIONS.md#anchor").
|
|
80
|
+
// audit_history.type is a free string (real audit types vary: 'routine',
|
|
81
|
+
// 'independent', 'first-deploy', ...); citation_backstop is a controlled vocabulary.
|
|
82
|
+
export const citationBackstops = ['research-kb', 'manual', 'unverified'] as const;
|
|
83
|
+
|
|
84
|
+
export const provenanceObject = z
|
|
85
|
+
.object({
|
|
86
|
+
ai_tools: z.array(z.string()).default([]),
|
|
87
|
+
prompts_archive: z.string().optional(),
|
|
88
|
+
decisions_log: z.string().optional(),
|
|
89
|
+
audit_history: z
|
|
90
|
+
.array(z.object({ date: z.date(), type: z.string(), file: z.string() }))
|
|
91
|
+
.default([]),
|
|
92
|
+
citation_backstop: z.enum(citationBackstops).optional(),
|
|
93
|
+
})
|
|
94
|
+
// .strict(): a misspelled key (e.g. `desisions_log`) must fail loud at build,
|
|
95
|
+
// not be silently stripped — silent data loss is the opposite of an audit trail.
|
|
96
|
+
.strict();
|
|
97
|
+
|
|
98
|
+
// Attached to every chapter schema as an optional field. The `.refine` makes
|
|
99
|
+
// "present ⇒ non-empty": a bare `provenance: {}` is author error (omit the key
|
|
100
|
+
// to opt out instead), so it fails fast rather than rendering a meaningless block.
|
|
101
|
+
export const provenanceSchema = provenanceObject
|
|
102
|
+
.refine(
|
|
103
|
+
(p) =>
|
|
104
|
+
p.ai_tools.length > 0 ||
|
|
105
|
+
p.audit_history.length > 0 ||
|
|
106
|
+
Boolean(p.citation_backstop) ||
|
|
107
|
+
Boolean(p.prompts_archive) ||
|
|
108
|
+
Boolean(p.decisions_log),
|
|
109
|
+
{ message: 'provenance is present but empty — omit the key, or set at least one field' },
|
|
110
|
+
)
|
|
111
|
+
.optional();
|
|
112
|
+
|
|
73
113
|
// ===== Chapter schemas — one per profile =====
|
|
74
114
|
|
|
75
115
|
export const academicChapterSchema = z.object({
|
|
@@ -90,6 +130,8 @@ export const academicChapterSchema = z.object({
|
|
|
90
130
|
updated: z.date().optional(),
|
|
91
131
|
tags: z.array(z.string()).default([]),
|
|
92
132
|
image: z.string().optional(),
|
|
133
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
134
|
+
provenance: provenanceSchema,
|
|
93
135
|
});
|
|
94
136
|
|
|
95
137
|
export const toolsChapterSchema = z.object({
|
|
@@ -109,6 +151,8 @@ export const toolsChapterSchema = z.object({
|
|
|
109
151
|
published: z.date().optional(),
|
|
110
152
|
tags: z.array(z.string()).default([]),
|
|
111
153
|
image: z.string().optional(),
|
|
154
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
155
|
+
provenance: provenanceSchema,
|
|
112
156
|
});
|
|
113
157
|
|
|
114
158
|
/** Minimal profile currently aliases the tools schema. */
|
|
@@ -168,6 +212,8 @@ export const courseNotesChapterSchema = z.object({
|
|
|
168
212
|
published: z.date().optional(),
|
|
169
213
|
updated: z.date().optional(),
|
|
170
214
|
image: z.string().optional(),
|
|
215
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
216
|
+
provenance: provenanceSchema,
|
|
171
217
|
});
|
|
172
218
|
|
|
173
219
|
/**
|
|
@@ -255,6 +301,8 @@ export const researchPortfolioChapterSchema = z.object({
|
|
|
255
301
|
author: z.string().optional(),
|
|
256
302
|
published: z.date().optional(),
|
|
257
303
|
image: z.string().optional(),
|
|
304
|
+
// v4.8.0: optional process-as-artifact audit trail (Provenance.astro).
|
|
305
|
+
provenance: provenanceSchema,
|
|
258
306
|
});
|
|
259
307
|
|
|
260
308
|
// ===== Inferred chapter types — one per schema =====
|
|
@@ -269,6 +317,7 @@ export type ToolsChapter = z.infer<typeof toolsChapterSchema>;
|
|
|
269
317
|
export type MinimalChapter = z.infer<typeof minimalChapterSchema>;
|
|
270
318
|
export type CourseNotesChapter = z.infer<typeof courseNotesChapterSchema>;
|
|
271
319
|
export type ResearchPortfolioChapter = z.infer<typeof researchPortfolioChapterSchema>;
|
|
320
|
+
export type Provenance = z.infer<typeof provenanceObject>;
|
|
272
321
|
|
|
273
322
|
// ===== Collateral collection schemas (tools-profile; always-defined) =====
|
|
274
323
|
|