18000x · 10 — Lessons + Carry-forward (4 new lessons: two-narrow-sidecars, fn_iu_compose=bulk-mechanism, mutation-invariant, live-tag-vs-delivery-separable)
18000x · 10 — Lessons + Carry-forward
Lessons (new this macro)
L1 — Versioning is a sidecar, not a composition concern
The 15000x productization marked templates with a sidecar; 18000x extends with a SECOND sidecar (iu_collection_template_version) for version-chain identity. Both sidecars key on collection_id. They could have been one wider table, but the two-table split:
- keeps mig 031 rollback row count separable from mig 032;
- lets a template exist WITHOUT a version chain (legitimate — see
tpl:file:status-report/v1); - lets a future "lightweight" entry (e.g. ad-hoc bookmark or favourite) live without dragging in versioning columns.
Rule: when adding a second productization concern to a marker table, prefer a second PK-shared sidecar over widening the existing sidecar. Two narrow sidecars = two narrow rollbacks.
L2 — fn_iu_compose iu_id-reuse branch IS the bulk-instance mechanism
12000x discovered it; 15000x proved 1 instance; 18000x proved N=3 instances in one TX. The pattern:
v_pieces := array_agg(m.iu_id ORDER BY m.piece_order)
FROM iu_piece_membership WHERE collection_id=<template> AND membership_status='active';
v_pieces_json := jsonb_agg(jsonb_build_object('role','body','iu_id', x) FROM unnest(v_pieces));
fn_iu_compose('tpl-inst:.../bulk-i', kind, title, desc, v_pieces_json, actor);
Rule: don't author a fn_iu_create_bulk_instances — fn_iu_compose already does it. Bulk = loop over fn_iu_compose with the same pieces_json. Each call is its own atomic compose.
L3 — Mutation invariant comes from digest-keyed-on-iu_id
fn_iu_collection_manifest_refresh hashes (piece_order, iu_id, piece_role) — see [[feedback-manifest-digest-keyed-on-piece-graph]]. Supersede mints a new iu_id and leaves memberships alone, so digest is byte-identical. The invariant is automatic — no policy code needed.
Rule: when a substrate's primitive (here: manifest_digest) is computed from immutable inputs (iu_id), in-place mutations of those inputs' DOWNSTREAM state cannot propagate without an explicit operator action.
L4 — Internal LIVE event proof is just two gate flips inside a TX
15000x emitted with dry_run_only=true; 18000x flipped dry_run_only=false inside the same in-TX gate-toggle pattern. The proof shape is unchanged; only the tag in safe_payload.emit_mode changes (dry_run → live). External delivery is governed by SEPARATE gates (iu_core.delivery_enabled + iu_core.delivery_live_routes) and remains untouched.
Rule: "live tagging" and "external delivery" are separable concerns in this substrate. Future macros can durably enable live tagging without touching delivery, and vice-versa.
Memory entries proposed (4)
- [[feedback-two-narrow-sidecars-over-one-wide-sidecar]] (NEW) — see L1
- [[feedback-fn-iu-compose-is-the-bulk-instance-mechanism]] (NEW) — see L2
- [[feedback-pinning-tests-bump-per-macro]] (REFRESH) — 18000x bumped 13 (was 12 at 15000x)
- [[project-dot_iu_cutter_v0_6_iu_core_18000x_template_versioning_bulk_scaleout_event_ops_closeout]] (NEW) — full project entry
Carry-forward to 19000x+
| # | Item | Target |
|---|---|---|
| 1 | Birth-gate PILOT noise (P-pub1/P-pub2 warnings) — silence for iu_core/template/* addresses |
19000x |
| 2 | Auto-instantiate productization: 1 event_type + 1 fn + 1 route + 1 gate (per 08-…) |
19000x |
| 3 | Live (durable, non-dry-run) piece event delivery — needs external worker contract | ≥19500x |
| 4 | tpl:file:status-report family — opt-in to version chain when v2 ships |
when v2 authored |
| 5 | Template piece mutation propagation worker (NOT silent; explicit operator command) | ≥19000x |
| 6 | Retention enable after 30+ day soak | ≥17000x (operator) |
| 7 | PR #669 merge / Nuxt deploy | external (different clone) |
| 8 | VPS Linux timer / Grafana monitoring fallback | 19000x |
| 9 | Bulk-instance retire / soft-delete DOT (current state: durable, addressable, no fast-cleanup verb) | 19000x |
| 10 | Cross-template piece sharing observability extension — show "this piece is used by N templates" | 19000x |
What 18000x intentionally did NOT do
- Did NOT add a new healthcheck surface — 8 is the right size; observability lives in views.
- Did NOT add a new event_type for
template.published— would require event_outbox CHECK constraint update (per 10000x defect lesson); deferred until the consumer is authored. - Did NOT productize
fn_iu_auto_instantiate_from_template— sandbox proof is the deliverable; durable function ships in 19000x. - Did NOT touch the 12000x untracked ops dir.
- Did NOT enable retention, deploy Nuxt, or merge PR #669 (all external/constitution-forbidden).
- Did NOT productize template piece mutation propagation — the invariant is "explicit v(N+1) compose", which is exactly what the version chain provides.