D1 thin dir+git with shard-wiki then kontextual upgrade path; D2 defer asset-store; D3 NetKingdom authz consolidation; D4 outline explode/implode and live-by-default includes with pin as exception.
9.3 KiB
Stale badge, alternatives, and derived includes
| Field | Value |
|---|---|
| Date | 2026-08-13 |
| Status | design proposal (coulomb product + markitect-tool contract) |
| Related | authoring modes, transclusion, markitect include/provenance |
Intent
Any derived content — projection fields (title, abstractor, visual),
transclusions/includes, or outputs of rules / external data / research /
pipelines / classic compute / AI — can become stale relative to its inputs.
The product (and markitect-tool) should:
- Detect staleness via input fingerprints (content hashes, dependency set).
- Surface a clear “may be stale” badge (never silent rewrite by default).
- Let the user generate an alternative (more current candidate).
- Let the user replace the stored version or keep it (and optionally lock).
Same interaction grammar everywhere: projections, frozen include snapshots,
and pipeline-produced blocks.
What markitect-tool has today (gap analysis)
| Building block | Status |
|---|---|
Include / transclusion markers + resolve_includes |
Yes |
| Selectors / partial include | Yes |
| Operation provenance on include/transform (path, selector, deps metadata) | Yes (partial) |
| Content hashes on units / explode entries / processing provenance | Yes (scattered) |
Cache fingerprint + mkt cache status (file changed?) |
Yes |
Backend refresh-plan (snapshot vs files) |
Yes (backend fabric) |
| Context package refresh recipes | Yes (agent memory) |
| Stale badge semantics for a stored derived snapshot vs live resolve | No productized UX contract |
| Generate alternative → replace or keep for includes/projections | No |
| Pinned / locked include snapshot in the document | No first-class marker |
| Unified “derived block” model (rule, AI, external, include) | No — needs design |
Conclusion: markitect has plumbing for change detection and provenance,
not yet a unified stale → alternative → replace|keep workflow for
in-document derived content. That belongs in markitect-tool as a reusable
contract; coulomb UI implements the badge and choices.
Cross-repo proposal:
markitect-tool/docs/proposed-stale-derived-and-include-pinning.md
Unified model: derived slot
A derived slot is any value that:
- has a current stored representation (frontmatter field, pinned body region,
or last accepted snapshot), and - has a recipe that can recompute a candidate from inputs.
DerivedSlot
id
kind: projection | include | pipeline | external | ai | rule
stored_value # what readers see by default
recipe # how to recompute
input_fingerprint # hashes of inputs when stored_value was accepted
state: derived | user | locked
locked: bool
generated_at
generator / recipe_version
Stale ⇔ recompute current_fingerprint(inputs) ≠ input_fingerprint
(or a dependency file is missing / recipe version bumped if policy says so).
User actions when stale (or anytime if unlocked)
| Action | Result |
|---|---|
| Keep | Leave stored_value; optionally refresh fingerprint (“accept as current”) or leave stale badge |
| Generate alternative | Run recipe → candidate (side-by-side; does not overwrite yet) |
| Replace with alternative | stored_value ← candidate; update fingerprint; state → derived |
| Edit stored | Manual edit; state → user |
| Lock / Unlock | Block regenerate and auto-refresh |
Default policy: never auto-replace locked or user slots;
never auto-replace stale derived without consent (badge + actions only).
Coulomb: projection fields
Extends authoring-modes-and-lazy-projections.md.
When projections.title.source_hash (etc.) ≠ current body/deps hash:
- Show May be stale on the field/card.
- Actions: Generate updated version · Keep current · Edit · Lock.
- Generate opens compare UI:
┌ Current (stored) ┌ Alternative (fresh)
│ … │ …
└──────────────────────┴──────────────────
[ Keep current ] [ Use alternative ] [ Cancel ]
- Use alternative writes frontmatter and updates
source_hash/generated_at. - Keep current dismisses compare; optional “Don’t warn until body changes again”
= update fingerprint only (ack_stale) without changing text.
Same for abstractor and visual (visual compare: two images or image vs placeholder).
Includes / transclusions
Two legitimate modes (both needed):
A. Live resolve (dynamic) — product default
<!-- mkt:include path="src.md" selector="sections[heading=API]" -->
At render/export, markitect resolves from current src.md
(spreadsheet-like: formula results stay up to date).
“Stale” for pure live includes is mostly viewer/cache (optional badge if
a cached card excerpt lags live resolve). No replace/keep needed unless the
host cached a display snapshot.
B. Pinned / materialised include (snapshot in host page) — exception
For “freeze this excerpt / paste values” (explicit pin):
<!-- mkt:derived id="api-excerpt"
kind="include"
path="src.md"
selector="sections[heading=API]"
input_hash="sha256:…"
locked="false"
generator="include@v1" -->
…frozen markdown of the included region…
<!-- /mkt:derived -->
Or shorter pin attributes on include + adjacent snapshot region.
| Event | Behavior |
|---|---|
| Source file changes | Slot becomes stale (hash mismatch) |
| User generates alternative | Resolve include live → candidate body |
| Replace | Write new snapshot into the derived region; update input_hash |
| Keep | Leave snapshot; optional ack |
| Lock | No refresh until unlock |
Live vs pinned: product default for co-creation cards may be pinned
(exportable, offline, “my version of this excerpt”); docs/wiki sites may prefer
live. Coulomb should support both; stage-1 can start with live resolve for
view and add pin when “freeze excerpt” is needed.
Pipeline / AI / external derived blocks
Same mkt:derived envelope:
<!-- mkt:derived id="summary-1"
kind="ai"
recipe="summarize@v2"
inputs="body"
input_hash="sha256:…"
locked="false" -->
…generated summary…
<!-- /mkt:derived -->
<!-- mkt:derived id="metrics"
kind="pipeline"
recipe="research.pull@v1"
inputs="config/query.yaml,data/…"
input_hash="sha256:…" -->
| metric | value |
…table…
<!-- /mkt:derived -->
Kinds (open set): include | rule | pipeline | external | ai | compute | projection.
markitect-tool contract (proposed)
Expose library APIs (names illustrative):
fingerprint_inputs(recipe, context) -> str
is_stale(slot, context) -> bool
evaluate_recipe(recipe, context) -> Candidate # markdown + provenance
diff_or_pair(stored, candidate) -> Comparison
apply_replace(document, slot_id, candidate) -> document
apply_keep(document, slot_id, *, ack_fingerprint: bool) -> document
CLI sketches:
mkt derived status page.md # list slots + stale?
mkt derived refresh page.md --id api-excerpt --dry-run # print candidate
mkt derived refresh page.md --id api-excerpt --replace
mkt derived refresh page.md --id api-excerpt --keep --ack
mkt derived lock page.md --id summary-1
Include path should record target content hash in provenance today;
stale detection reuses cache fingerprints + per-slot input_hash.
Implementation can land incrementally in markitect-tool (see proposal doc);
coulomb consumes the API for badges and compare UI.
UX summary
[May be stale]
→ Generate updated version
→ side-by-side Current | Alternative
→ Keep current (optional: clear badge / ack)
→ Use alternative (replace stored)
→ Cancel
→ Lock field/block
Applies to: title, abstractor, visual, outline stub, pinned includes,
AI/pipeline regions — one mental model.
Relation to lock
| Unlocked derived | Locked | User-edited | |
|---|---|---|---|
| Stale badge | yes if fingerprint mismatch | yes if mismatch (informational) | optional / policy |
| Generate alternative | yes | no (must unlock) | yes (warns overwrite of user text) |
| Auto-replace | never (default) | never | never |
Stage-1 vs later
| Slice | When |
|---|---|
| Stale badge + regenerate/replace/keep for title/abstractor | Stage-1 authoring |
| Visual same | Stage-1 if visual exists |
| Live include resolve only | With markitect integration |
Pinned mkt:derived include snapshots |
Soon after includes land |
| Full pipeline/AI recipes | As generators are added |
Related
docs/architecture/authoring-modes-and-lazy-projections.mddocs/architecture/transclusion-and-chunks.md- markitect-tool:
docs/transform-compose-include.md,docs/cache-incremental.md,docs/backend-fabric.md - markitect-tool:
docs/proposed-stale-derived-and-include-pinning.md(proposal for implementation there)