Provider-neutral periodic report v0¶
Product activation¶
An explicit request in an active project session, such as "generate this
week's project report", is sufficient authority for one provider-free local
generation. LoopX resolves the built-in weekly-progress profile (aliases
weekly and weekly-report), whose normalized periodic_report_profile_v0
sets enabled: true, binds project_progress_v0, Markdown, and HTML, and
declares neither a schedule nor a sink. The profile can be inspected without
effects:
This does not persist project activation, create a scheduler, or grant an
external write. The current session owns the requested calendar window. When
the built-in preset is selected, the activation packet includes an
interaction_contract that makes those session-generation defaults explicit;
the generic heartbeat prompt does not need a report-specific expansion.
A project-owned periodic_report_profile_v0 is the advanced opt-in contract
for custom or unattended operation. Enabled custom profiles declare:
- provider-neutral trigger policy and an optional host-owned RRULE/timezone;
- one or more domain source adapter bindings;
- one or more renderer bindings;
- zero or more required, optional, or disabled extension sink bindings.
Use a host Automation only when the report must run unattended on an RRULE. The Automation schedule and project profile must agree; ordinary in-session generation does not need an Automation. External delivery still requires an explicit sink binding and its independent runtime authority/readback checks.
periodic_report_activation_v0 is the effect-free inspection receipt. It
records whether generation is allowed, the normalized profile digest, and the
portable/enhanced/durable extension mode. It performs no source read, schedule
mutation, provider lookup, rendering, archive write, or message delivery.
periodic_report_project_progress_projection_v0 is the built-in,
domain-neutral source input. It groups typed project facts into progress,
capability evolution, risks, next actions, and supporting evidence, with no
more than eight primary audience items. Issue Fix has no special standing in
either schema. It may register a peer source adapter under the same contract as
release, research, operations, or another domain. OpenViking is likewise an
optional archive/query provider behind a sink extension; it does not own
trigger, selection, rendering, or delivery.
periodic_report_v0 is the LoopX control contract for one bounded report run.
It binds a period window and a profile to typed source snapshots, one rendered
artifact receipt, archive and delivery receipts, deterministic idempotency,
explicit partial/unknown states, and a bounded retry projection.
The capability also defines periodic_report_trigger_decision_v0. A caller
evaluates compact LoopX or provider facts before collecting or delivering a
report:
loopx periodic-report evaluate-trigger \
--request-json periodic-report-trigger-request.json \
--format json
The command is local and effect-free. Source collection, rendering, archive writes, message delivery, and receipt readback all execute in adapters or connectors outside this core.
Split phase contract¶
periodic_report_v0 also exposes a split contract for profiles that must keep
local report generation independent from provider availability:
periodic_report_generation_bundle_v0contains the normalized document, one to eight artifacts, and a deterministicperiodic_report_generation_receipt_v0. The receipt declares that no provider or external write was required.periodic_report_sink_binding_v0pins a sink id, role, dependency policy, capability id/version, extension id/version, and provider-neutralperiodic_report_sink_v0protocol.periodic_report_extension_readiness_v0verifies those bindings against observed provider receipts. It reportsportable,enhanced, ordurabledelivery mode and never performs a provider call.periodic_report_delivery_receipt_v0binds provider sink results back to the generation and readiness receipts. A sent sink is accepted only with an idempotency key, compact receipt reference, and verified exact readback.
The dependency policy is required, optional, or disabled. Required sinks
block formal delivery when unavailable. Optional sinks degrade without
invalidating the generation receipt. Disabled sinks are skipped. Provider
versions, protocols, or capabilities that do not match the profile binding are
incompatible; providers without verified readiness are unverified.
The bundled openviking-periodic-report LoopX extension is one concrete
implementation of this port. Its runtime protocol is
periodic_report_sink_v0, its manifest permission and observed runtime
capability are both openviking_context_write, and its sink capability is
report.archive.write/v0. Runtime activation recomputes the normalized
periodic_report_activation_v0; a disabled or altered receipt, a missing or
disabled sink binding, a stale extension doctor proof, or a missing observed
runtime capability rejects the invocation before any provider write.
openviking_periodic_report_archive_request_v0 carries the activation receipt,
normalized document, Markdown artifact, archive context, and an execution bit.
The provider writes two OpenViking Resources in commit order: report.md, then
manifest.json. The latter records
openviking_periodic_report_archive_commit_v0, the bundle digest, stable
result id, and manifest_written_last=true. A sent sink result requires an
exact content-digest readback of both URIs. HTML hosting, historical queries,
and memory distillation are separate consumers of the committed Resource and
are not part of this provider protocol.
The older run request below remains a compatibility full-delivery envelope. It still requires archive and delivery receipts, while the split contract makes the provider-free generation truth available before those receipts exist.
Trigger decision¶
A periodic_report_trigger_request_v0 binds a profile and trigger policy to an
evaluation timestamp, optional last-report receipt, and up to 64 compact
candidate facts. The built-in kinds are:
cadence_due: a profile-owned schedule says a report window is due;vision_closed: the vision transition is closed, acceptance is validated, and a successor is established or the goal is terminal;primary_goal_outcome: the primary delivery outcome is validated and has a durable writeback;material_decision: an approved, rejected, or cancelled decision changed the execution route and was durably recorded;material_blocker: a new or escalated P0 blocker stops the primary path;material_recovery: a validated resolution reopens the primary path;manual: an explicitly authorized run.
surface_only, state_refreshed, todo_completed, monitor_unchanged, and
vision_checkpoint are accepted only so the decision receipt can explain why
they were suppressed. They never trigger a report by themselves.
The decision sorts material candidates by urgency, coalesces concurrent facts,
and derives a stable report_key. Trigger identity is derived from kind,
source reference, and evidence digest; a last-report receipt suppresses ids it
already covered. A profile-owned minimum interval suppresses non-urgent
updates, while authorized manual runs, validated primary outcomes, validated
vision closures, and primary-path blockers may bypass it. The output records
the selected and coalesced ids, every suppression reason, cooldown state, and
the report kind (cadence_digest, milestone_update, exception_update, or
manual_update).
An eligible decision may be embedded as trigger_receipt in a
periodic_report_run_request_v0. Its report_key and report_kind then
participate in run identity, so a milestone update and a scheduled digest over
the same evidence window cannot collide.
Request and identity¶
A periodic_report_run_request_v0 contains:
generated_atand an offset-awareperiod_window.start_at/end_at;- a stable
profile_id,profile_version, and optional opaqueprofile_ref; - one or more
source_snapshots[]with source identity, typed status, compact digest/reference/count evidence, and retryability; - one
artifact_receiptnaming a renderer and artifact state; - at least one
archiveand onedeliveryreceipt; retry_policy.attemptandmax_attempts.
It may also contain an eligible periodic_report_trigger_decision_v0 receipt.
LoopX derives run_id and the run-level idempotency_key from the normalized
window, profile, source identities, renderer identity, and sink identities.
Snapshot contents and attempt number do not change that identity, so a retry
cannot create a second logical report. Callers may repeat the derived values;
stale or mismatched values fail closed.
Every sink receives a deterministic sink-specific idempotency key derived from
the run, sink role, and sink id. A sent receipt is valid only with an exact
key, a compact receipt reference, and verified readback.
State and retry semantics¶
Source statuses are complete, partial, failed, or unknown. Artifact
statuses are pending, rendered, failed, or unknown. Sink statuses are
pending, sent, failed, skipped, or unknown.
The derived run state is one of:
pending: rendering or a sink has not settled;succeeded: all sources are complete, the artifact is rendered, and every archive/delivery sink is sent with verified readback;partial: usable output exists but a source is partial, a sink was skipped, or at least one sink succeeded while another failed;failed: collection/rendering failed, or every required sink failed;unknown: a source, artifact, or sink postcondition cannot be determined.
Retry is allowed only for terminal non-success states, before max_attempts,
and only when at least one unsettled component explicitly declares itself
retryable. The output names those components and the exact next attempt.
Ownership boundary¶
The core deliberately contains no project, pull request, issue, weekday, timezone, chat, document, or provider policy. Those belong to reusable adapters and project profiles:
- a project profile owns cadence, timezone, report sections, audience, and selection policy;
- source adapters collect and normalize domain evidence;
- renderers turn normalized evidence into artifacts;
- archive and delivery sinks perform gated writes and verify readback;
- project products may index historical artifacts without changing run identity or delivery truth.
The built-in presentation adapters include linear Markdown and a
self-contained html_artifact_v0 renderer. The HTML renderer is a zero-build,
single-file projection with optional local interaction. Its default
editorial_dense_v2 presentation keeps normalized primary item facts visible,
accepts profile-owned language and at most four first-screen highlights,
compiles its audience summary from typed primary items, and moves supporting
items, profile identity, source health,
generation metadata, and digests into a collapsed appendix. Items may declare
visibility=primary|supporting; runtime and delivery_receipt content kinds
must be supporting. The linear Markdown artifact preserves those items in a
labeled appendix so copy/export remains complete without interrupting the main
narrative. HTML embeds that Markdown rendering and records its companion
artifact digest. Audience items may also carry up to four ordered details
rows for readable fact grouping and optional tag_labels for localized display;
the canonical token tags remain unchanged.
Hosting or generating a shareable URL remains a separate sink action with its
own idempotency and readback receipt; it is not renderer authority.
The bundled Lark extension includes an opt-in miaoda_html delivery sink for
html_artifact_v0. It validates the single HTML, compressed archive, and
uncompressed payload limits before any external effect. A successful receipt
requires exact readback of the profile-owned app id, published URL, and
published state, and also records the observed access scope and login
requirement. The project or host still owns app selection, authentication,
audience policy, and the execute decision.
The normalized document's optional editorial input is split by ownership.
The project profile owns bounded kicker, period_label, language, and zero
to four ordered public-safe highlights. The document builder owns summary and
its periodic_report_editorial_orchestration_v0 receipt. It deterministically
selects typed primary outcome/decision, risk, and next_action titles,
falls back to an item's typed next_action field when needed, records exact
item lineage, and rejects an authored summary. Both built-in
renderers recompute the value before rendering, so changing the summary without
changing its source facts fails closed. Primary summaries are limited to 360
characters, and primary capability_change items require at least two named
details.
This orchestration is structural, not semantic guessing. Source adapters own
content_kind; the compiler never promotes runtime or delivery_receipt
items, and untyped/progress/capability-change facts remain in the body without
being pulled into the hero. This object is for audience conclusions, not
artifact construction or sink status.
Delivery parity, archive-provider validation, digests, canaries, renderer
lineage, and exact readback remain supporting items or sink receipts.
The core rejects raw content, messages, logs, transcripts, credentials, secret fields, and private paths. Public packets retain only compact references and digests.