Appendix A - 18. Reflection-facet substrate (tempo-gated policy nudges)

The Structure of Reflection-facet substrate (tempo-gated policy nudges) — its shape at a glance:

Facets self-register into a typed registry; a shared emitter, bound to a lifecycle event, round-robins them and emits at most one reflection per window — the aggregate ceiling no independent hook can share.

flowchart LR
  F1[Facet: failure→mechanism] --> Reg[(Facet registry)]
  F2[Facet: knowledge-routing] --> Reg
  F3[Facet: structural-drift] --> Reg
  Reg --> Emitter{{Shared emitter}}
  Emitter -->|≤1 per window, round-robin| Nudge([One paced reflection])

Accessible description: several policy facets self-register into a typed registry; a shared emitter bound to a lifecycle event round-robins them and emits at most one reflection per window, so the family shares one anti-overwhelm ceiling instead of each hook firing on its own.

Projected from the catalogue entry lifecycle-and-observability / Reflection-facet substrate (tempo-gated policy nudges).

On this page: Intent · Motivation · Applicability · Structure · Sample Code · Consequences · Example use within DocAble · Related Patterns

Intent

Intent — Consolidate the operator's policy-reflection nudges into ONE tempo-gated substrate: a registry of Template-Method facets, each reflecting the running context against a single repo-policy dimension it references (never copies), the whole family emitting at most one reflection per window, so several soft reflections can't compound into the alarm fatigue that would kill them all (our instance: a ReflectionFacet base + facet registry with four facets over a shared turn-end window: failure→mechanism, knowledge-routing, structural-drift, operations).

Motivation

A single lifecycle hook that re-arms one omitted reflex is cheap and clear. The trouble starts at the second one. Once you want the operator to reflect on more than one recurring policy (convert this recurrence into a mechanism, route this lesson to the right store, this looks like a second copy, this runbook is stale), the naive path is N independent hooks, each firing on its own event.

Three failures follow, and they compound:

Applicability

Structure

The Structure diagram appears at the top of this page.

Sample Code

A Template-Method base fixes the reflect sequence; a facet fills only the sanctioned steps and declares a pointer to its policy (never a copy). A shared emitter round-robins the registered facets and emits at most one nudge per window — the ceiling the whole family shares.

from abc import ABC, abstractmethod

class ReflectionFacet(ABC):
    key: str
    policy_ref: str          # a resolvable pointer into the canonical policy — referenced, not copied

    @abstractmethod
    def warrants(self, ctx) -> bool: ...      # is this facet's moment? bias hard toward False (silence)
    @abstractmethod
    def payload(self, ctx) -> str: ...        # the conservative nudge, worded from policy_ref

class Emitter:
    def __init__(self, facets): self._facets, self._cursor = list(facets), 0

    def tick(self, ctx) -> str | None:
        n = len(self._facets)
        for i in range(n):                     # round-robin so no single facet monopolizes the window
            f = self._facets[(self._cursor + i) % n]
            if f.warrants(ctx):
                self._cursor = (self._cursor + i + 1) % n
                return f.payload(ctx)          # AT MOST ONE reflection per window across the whole family
        return None

Consequences

Example use within DocAble

Contents
© James C. Davis, 2026–present