Skip to content

Native discrete-event engine rework - #17

Open
thevolatilebit wants to merge 138 commits into
mainfrom
rework
Open

Native discrete-event engine rework#17
thevolatilebit wants to merge 138 commits into
mainfrom
rework

Conversation

@thevolatilebit

@thevolatilebit thevolatilebit commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

ReactiveDynamics.jl: native discrete-event engine rework

What this PR does

This rework pivots ReactiveDynamics.jl from a SciML and Catlab base to a native, dependency-light discrete-event engine for timed, stochastic, resource-constrained modeling of business and R&D processes — budgeting, ledgers, what-if analysis, rNPV — with first-class support for agentic, structured resources and for composition. The model is now accommodated within AlgebraicAgents.jl, which provides the modeling interface and unlocks these goals: agentic resources and composability with third-party models.

The motivation: years of unresolved technical debt were limiting the framework's applicability and publication prospects, and burying its conceptual novelty under technical-level constraints. The rework revisits how the core pillars are implemented — the DSL, the data store, and the simulation engine, focusing on correctness and intended semantics — introduces proper semantic tests, and delivers a documentation site.

Everything is specified contract-first: a normative operational-semantics contract (spec/CONTRACT_DRAFT.md §1–§15) and 15 Architecture Decision Records (spec/adr/), with the engine built to satisfy them. Implementation state is tracked in spec/STATUS.md.

The classical modeling surface stays familiar. A plain-species SIR, end to end:

using ReactiveDynamics

sir = @reaction_network begin
    α * S * I, S + I --> 2I, name => I2R    # a bare numeric rate is a stochastic (Poisson) intensity
    β * I,     I     --> R,  name => R2S
end
@prob_init  sir S = 999 I = 10 R = 0
@prob_params sir α = 0.0001 β = 0.01
@prob_meta  sir tspan = 250 dt = 0.1

prob = ReactionNetworkProblem(sir; seed = 1)   # seed= owns the per-run RNG — the only route to reproducibility
simulate(prob)
prob.sol[!, "I"]                                # read solution columns BY NAME (order is construction order)

Headline changes

Modeling capabilities

This is the consequential part of the rework: the framework now natively expresses what previously required host-side workarounds — structured tokens with identity, value-qualified selection, in-model decision rules, resource contention semantics, and hierarchical composition.

  • Structured, agentic tokens with live instantiation and query; a host-function registry replaces @register-time eval (ADR 0006). A token is a first-class entity with attributes and a stable identity, not an anonymous count:

    @register begin
        @aagent BaseStructuredToken AbstractStructuredToken struct ProjectToken
            phase::Symbol      # the canonical "phase-as-attribute" (ADR 0008 §D)
            npv::Float64
        end
    end
  • Token filtration: TokenPredicate and @select pick tokens by an 𝓕ₜ-measurable predicate; phase-as-attribute is canonical; @advance and SetField write fields while preserving identity (ADR 0008). A pipeline step selects a value-qualified subset and advances the same object in place:

    @deterministic(1.0),
        @select(Project, phase == :Phase2 && npv > 150.0) --> @advance(phase, :Phase3),
        name => fasttrack, cycletime => 1.0, probability => 1.0
  • Resource modality as a per-participation tag on the LHS — @conserved (returned at finish), @rate (drawn per in-flight tick), @nonblock (claimed, not held) — so contention is properly modeled, not hand-coded:

    pipeline = @reaction_network begin
        @deterministic(2.0),
            @select(Project, phase == :Phase2) + 4 * @conserved(scientist) + 5 * @rate(budget) -->
            @advance(phase, :Phase3),
            name => adv_phase2, cycletime => 2.0, probability => 0.4, priority => 2.0
    end
  • Genesis as a first-class transition product: @structured(:Kind, field = …) mints a fresh token on the RHS (the agentic ∅ --> species), registry-resolved so it too serializes eval-free (ADR 0006, ADR 0005). Field expressions read live state (@t()) and the seeded RNG:

    @deterministic(1.0),
        ∅ --> @structured(:Project, phase = :Phase1, npv = rand(state.rng, Normal(120.0, 20.0)), born = @t()),
        name => genesis
  • Rules, triggers, and conditional transitions form the endogenous decision channel (ADR 0010), with action callbacks SetTokens and Invoke (ADR 0011). A management lever lives in the model as a typed Rule, not as host patch code:

    Rule(:series_b, :(@t() > 2.0),
         Seq([SetSpecies(:cash, 500, :inc),          # inject capital
              SetParams([:synergy => 1]),            # flip a parameter the rates read
              AddToken(:Project, [:phase => QuoteNode(:Phase2), :npv => 175.0])]);  # add a program
         fire_mode = :once)
  • Hierarchical refinement and open-port composition: @pipeline, @process, and @compose author coarsely; refine substitutes a finer sub-process for one step via FK splice, non-mutating and leaving boundary species in place (ADR 0009):

    portfolio = @pipeline Project begin
        Discovery => Phase1:(ct = 1.0, pos = 0.45)
        Phase1    => Phase2:(ct = 1.5, pos = 0.6)
        Phase2    => Phase3:(ct = 2.0, pos = 0.4)
    end
    refined = refine(portfolio, :flow_Phase2_Phase3, phase2_detail;
                     ports = Dict(:Phase2 => :p2_in, :Phase3 => :p2_out))
  • Interface lifecycle (authoring → construction → live), a declarative, serializable population[] initial marking, and dump_state with restore (ADR 0007). The starting portfolio is reproducible input, not imperative post-construction code:

    ReactionNetworkProblem(pipeline_model(); seed = 1, registry = REGISTRY,
        population = [ProjectToken(:Phase1, 120.0), ProjectToken(:Phase2, 200.0), ProjectToken(:Phase3, 300.0)])
  • AlgebraicAgents integration: ReactiveDynamics as an AA hierarchy node — outbound via getobservable and parameters, inbound via wired external coupling (inputs[], ExternalRef, _prestep! with a one-tick Jacobi lag) (ADR 0012). The foreign-agent topology lives host-side in add_wire!, never in the model document:

    add_wire!(root; from = market, to = rd,      from_var_name = "sentiment", to_var_name = "sentiment")
    add_wire!(root; from = rd,     to = finance,  from_var_name = "cash",      to_var_name = "rd_cash")

    AA moved to the published registry 0.4 release. @agentize provides thin auto-naming sugar over the ReactionNetworkProblem constructor (ADR 0001, ADR 0012) with no second construction path.

Engine and semantics

  • Native discrete-event engine (ReactionNetworkProblem stepped via AA's _step!); the SciML stack (DifferentialEquations, OrdinaryDiffEq, DiffEqBase) and the old DiscreteProblem transform were removed entirely — no dependencies, no code (ADR 0001). A SciML interop adapter is left as a possible future package extension, but none ships here.
  • Priority-weighted progressive-fill (water-filling) resource allocator — work-conserving, deterministic, dependency-free (ADR 0002).
  • Append-only mutation with soft deactivation, so transitions, species, and parameters can be added — and transitions retired — mid-simulation without breaking position-indexed compiled closures (ADR 0004).
  • AbstractRNG and seed= threaded through every draw; a run is fully determined by (model, seed) (CONTRACT §4).
  • Construction-time modality validation (validate_modalities, CONTRACT §1.4): rejects the three illegal modality configurations ({:nonblock,:conserved}; :rate with concrete cycletime==0; :rate on a structured species) with a clear ArgumentError before any tick, replacing late or silent failures.

Data store and serialization

  • ACSets and Catlab dropped in favor of a dependency-free, typed struct-of-columns IR; the transition–reactant relation promoted to a first-class typed ReactantSpec incidence table (ADR 0003).
  • A single eval-free JSON serialization with a typed ExprNode IR: from_json_model and to_json_model round-trip, plus validate. This closes the import-time RCE and retires the TOML, CSV, and JLD2 format zoo (ADR 0005).
  • Post-ACSets naming pass (ADR 0015): @reaction_network (was @ReactionNetworkSchema), net (was acs), store type ReactionNetwork; store verbs renamed to store vocabulary (nrows, row_ids, column, cell, find_rows, …) and unexported. Old names survive one release as @deprecate shims; GeneratedExpressions dropped.

A model IS data: the same pipeline as an eval-free JSON document, which validate checks and from_json_model loads to a run bit-identical to the DSL build (host token kinds referenced by name through a registry — the document carries no Julia):

{ "rd_format":"reactive-dynamics-model", "version":"1.0",
  "meta":{ "tspan":6.0, "dt":1.0 },
  "species":[ {"name":"Project","structured":true} ],
  "transitions":[
    {"id":"adv12","rate":1.0,"rate_mode":"deterministic","cycletime":1.0,"prob_of_success":1.0} ],
  "reactants":[
    {"transition":"adv12","side":"lhs","predicate":{"kind":"Project","clauses":[["phase","==","Phase1"]]}},
    {"transition":"adv12","side":"rhs","advance":{"field":"phase","value":"Phase2"}} ] }
diags = validate(JSON.parse(json); registry = REGISTRY)   # [] ⇒ clean; a dangling FK is a Diagnostic, never an eval
prob  = from_json_model(json; seed = 7, registry = REGISTRY, population = pop)
to_json_model(prob)                                        # the inverse — a live model back to a document, loss-free

Analysis and visualization

  • Per-token trajectory log with representative_token and trajectory_envelope; an ensemble runner (ensemble, summarize, treatment_effect, with EnsembleProblem as an AA node); a results export bundle — CSV and JSON in core, Arrow as a weak dependency (ADR 0013). The ensemble runner supports both rebuild-per-seed (mode a) and reinit-reseed member reuse (ensemble(...; mode=:reinit), mode b via _reinit!(state; seed=…)), with member-for-member equivalence of the two modes under test.
  • Model-agnostic Plots recipes behind RDPlotsExt; the three-layer network "exec map" (network_graph, draw_network, exec_map) with bottleneck and starvation coloring and @select token highlighting (ADR 0014).
  • Plots and Arrow demoted to weak dependencies with the RDPlotsExt and RDArrowExt package extensions; Pluto, PlutoUI, IJulia, and DifferentialEquations dropped from dependencies.

Documentation and records

  • Contract-first: the normative operational-semantics specification (spec/CONTRACT_DRAFT.md §1–§15) and 15 ADRs (spec/adr/), all statuses truthed-up to Implemented with commit citations. The durable engineering artifacts live under top-level spec/, kept separate from docs/ (the Documenter static-pages site).
  • Repo-root CLAUDE.md agent guide; spec/STATUS.md as the single state index; completed handoff plans archived under spec/history/.

Tests

test/semantic/*.jl, two-tier — T1 characterization and T2 acceptance — with real assertions over operational semantics, not smoke tests. The suite is fully green (801 pass / 0 broken, no skips). test/Project.toml declares the test-only dependencies (Plots, Arrow, DataFrames, Distributions) so the weak-dependency extension paths are exercised, not skipped. A GitHub Actions scaffold (tests matrix, docs, Runic, TagBot, CompatHelper) has been added; the suite and the formatter (Runic) are also run locally.

Documentation and tutorials

Documentation and tutorials are now part of this rework (previously slated for a standalone follow-up; that work — branch docs-tutorials — has been consolidated into rework). The seven runnable demo/ tours are migrated in place as the single source of truth: their .jl sources are the Literate inputs the site ingests, so there is no duplicated model code to drift. The site is a Documenter.jl + Literate.jl build (HTML, GitHub Pages); every code block executes against the current engine at build time, pinned by an explicit seed=. It is chartered and tracked in spec/DOCS_CHARTER.md.

The structure is Diátaxis, routed on the landing page by reader intent:

  • Tutorials (learning) — three tiered, cumulative onboarding tutorials (introductoryadvancedexpert) plus two technical deep-dives (serialization; composition & granularity). Each tutorial ends by computing a decision-relevant quantity — a marginal effect, a treatment effect with its standard error — not a feature recap.
  • Applied case studies (understanding) — three question-titled, headline-number-first decision case studies: "What is the marginal eNPV of the Nth scientist?" (the shadow price of the binding resource; flagship), "What is this in-licensing asset worth to this pipeline?" (M&A — rNPV is not additive under contention), and "When should you kill a program?" (the value-maximizing kill threshold).
  • API reference (information) — capability-organized autodoc pages off the real (post-ADR-0015) export surface: authoring, structured tokens, rules & actions, construction & simulation, composition, serialization, analysis & visualization, and AlgebraicAgents coupling — plus a JSON model-schema page.
  • The "why" — rather than re-hosting the semantics as web pages, the site links to the normative contract (spec/CONTRACT_DRAFT.md §1–§15) and the ADRs at their spec/ home, and to two companion papers: a methodology paper (the operational semantics and backing concepts, arXiv-first) and an adoption/value paper for non-technical executives (motivation → solution → examples).

thevolatilebit and others added 30 commits September 16, 2023 23:57
Signed-off-by: Bíma, Jan <jan.bima@merck.com>
Signed-off-by: Bíma, Jan <jan.bima@merck.com>
Signed-off-by: Bíma, Jan <jan.bima@merck.com>
- fix support for initial values specified in acs
- fix support for custom tsteps
- fix req/alloc

Signed-off-by: Bíma, Jan <jan.bima@merck.com>
Signed-off-by: Bíma, Jan <jan.bima@merck.com>
[skip ci]
* fixes for new versions of Catlab/ACSets

* drop Catlab dep, add ACSets; use parts rather than 1:nparts

* fix tutorials and tests
---------

Co-authored-by: Bíma, Jan <jan.bima@merck.com>
- An agent now implements a property pointing at
the species.
- Using `@structured` macro, it is possible to set
call a custom agent constructor and assign the species.
- Using `@move` macro, it is possible to
reuse LHS agents on the RHS and modify their species.
Phase-0 orientation artifacts for the rework (no src/ changes; behind the
maintainer sign-off gate):

- INVENTORY.md: ground-truth src/ map for the ref-agents branch (module map,
  public-API audit, ACSet schema, stepping trace, AlgebraicAgents touchpoints,
  REVIEW.md defect reconciliation). Supersedes REVIEW.md's stale architecture
  map (which described the SciML/DiscreteProblem era).
- docs/adr/0001: ratify the native discrete-event engine; SciML demoted to an
  optional extension.
- docs/adr/0002: priority-weighted allocation via weighted progressive filling
  (water-filling) — replaces the 7-function per-resource-split tangle; clean
  priority semantics (fill-rate weight), work-conserving, conjunctive-consistent,
  deterministic, dependency-free. Records maintainer decisions: priority is
  dynamic/time-varying, per-tick fairness, priority=0 = leftover-only. Algorithm
  verified numerically on 10 scenarios incl. adversarial cases.
- REVIEW.md: prior multi-agent review/roadmap (carried forward; defect catalog
  still useful, file:line numbers stale vs ref-agents).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase-0 deliverables (docs only; behind maintainer sign-off gate). Produced
by a multi-agent design panel + evidence sweep, then adversarially verified
against current ref-agents source (zero inaccurate claims found).

- docs/adr/0003-data-store.md (Status: PROPOSED — needs maintainer confirm):
  recommend dropping ACSets for a dependency-free typed-struct-of-columns IR,
  phased: (1) behavior-preserving store swap, (2) promote the transition-reactant
  relation to a first-class typed ReactantSpec incidence table, (3) optional
  to_acset weakdep adapter for AlgebraicPetri interop. Rationale: zero homs +
  zero categorical ops in src/, composition is manual name-matching, runtime
  already Dict-based; dep is light ACSets (not Catlab) so churn premise doesn't
  transfer — decision is fitness/agentic-authoring, not dep weight. Lists
  store-independent fixes (close eval-on-import RCE, modality validation,
  free_blocked_species! bug, Manifest.toml, Phase-0 tests) + open questions.

- docs/CONTRACT_DRAFT.md: fork-independent modeling-contract sections —
  §1 modality truth table (orthogonal allocation/return/blocking axes; 5 legal
  rows + illegal rules; maps to ADR 0002 build_requirements!), §2 time model
  (single clock, Poisson spawn intensity, cycle/lifetime, dt-invariance hazards),
  §3 operational semantics (instance lifecycle + 13-step tick + 7 contract
  invariants, flagging the surviving engine bugs), §4 determinism & seeding
  (D1-D9: RNG threading, per-trajectory seeding), §5 typed attribute contract
  (units/range/default/time-varying per attribute). Object model, composition,
  and serialization sections deferred pending ADR 0003 sign-off.

- docs/adr/README.md: index updated with ADR 0003.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…R); genesis modes

Folds in maintainer rulings and adds the two ADRs the data-store decision
implied. Adversarially verified against ref-agents source (ADR 0004 fully
accurate; two ADR-0005 citation errors fixed; precision nits qualified).

- ADR 0003 -> Accepted: drop ACSets, promote the reactant relation, no
  backward on-disk compatibility. Serialization split to 0005, mutation to 0004.
- ADR 0004 (Accepted): runtime mutation contract — append-only + soft-deactivate
  (transActivated) invariant so transitions/species/params can be added DURING
  simulation without breaking position-indexed compiled closures; live mutation
  API (add_species!/add_transition!/...); wrap_fun/varmap refresh; compile-on-add.
  North-star: acquisition = mid-run add_transition!/perturb. Caveat added that the
  hot loop is not yet fully wrap_fun-free (pre/post-action + stoich still wrap
  in-loop); sol-column widening is a structural DataFrame op.
- ADR 0005 (Accepted): single JSON serialization + typed ExprNode IR (closed
  Const/Ref/Call/Sample/TimeRef/Choose sum type) — eval-free, JSON-Schema-
  describable for LLM emission/validation, closes the loadsave eval RCE. Drops
  TOML/CSV/JLD2 zoo. Concrete pharma-pipeline JSON example with an
  expression-valued rate serialized as a typed tree. Solutions -> Arrow, separate.
- CONTRACT_DRAFT.md §2.8 (NEW): genesis modes — Poisson is the wrong DEFAULT for
  business pipelines; genesis is already two-stage (proposal + upfront-LHS gate),
  so flow/capacity routing already work. Closed {poisson,scheduled,flow,capacity}
  intent tag over one execution path; source vs routing distinction; rejects the
  heavier dual-primitive redesign. Object-model/composition/serialization sections
  now unblocked (ADR 0003 decided).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes out the Phase-0 modeling-contract deliverables (docs only + tests;
behind the maintainer sign-off gate). Drafted by a multi-agent workflow whose
verifiers RAN the engine on Julia 1.12.5; corrections applied (simulate takes
max-time not step-count and returns the agent; seed= is silently swallowed;
the "events mutate u" claim was false — events are non-functional).

Contract (docs/CONTRACT_DRAFT.md) now §1-§8, complete:
- §6 Object Model: typed columnar tables under the ADR-0004 append-only index
  invariant; the promoted ReactantSpec incidence table (FK trans->T, species->S,
  side, stoich ExprNode, modality) replacing the per-tick trans-Expr re-parse;
  identity-by-name rule; structured-token refinement.
- §7 Composition Semantics: join (name-based S/P/M merge, disjoint T) + the
  obs/:E merge gap; equalize/identification; why the promoted reactant table
  makes species-merge structurally exact (FK repoint vs string surgery); the
  ADR-0004 live-guard on rem_parts!; the undefined include_model bug.
- §8 Serialization Schema: cross-references ADR 0005 (single JSON + ExprNode);
  round-trip + (model,seed)-determinism guarantees; eval-free validate pass;
  RCE closure; outputs->Arrow separately; the append-only mutation-patch form.

Phase-0 semantic test suite (test/semantic/, wired into runtests.jl):
- 63 testsets across allocation/conservation/lifecycle, modality/genesis,
  determinism/composition/bug-pins, and reference-models (SIR/toy-pharma/rNPV).
- Two tiers: T1-characterization runs against the current engine (locks in
  behavior or pins a known bug via @test_broken so the suite is green-when-
  expected); T2-acceptance encodes target behavior, wrapped with @test_skip +
  a reference block since it names not-yet-built APIs (progressive_fill!, seed=,
  ReactantSpec FK-repoint). All files parse-clean on Julia 1.12.5.
- Replaces the assertion-free tutorial smoke tests as the real acceptance layer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Single-page map for maintainer sign-off: the 5 ADRs (0001-0005) with statuses,
the complete §1-§8 modeling contract, the 63-testset semantic suite (39 T1
characterization / 24 T2 acceptance), the 9 known engine bugs the suite pins,
and a consolidated "decisions needed" section. Counts and bug-site citations
verified against ref-agents source. Surfaces the resource-retirement asymmetry
(transActivated exists, no specActivated) as an open question on ADR 0004.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…act §9

Answers the maintainer's structured-token requirement and the post-@register
custom-function question. Adversarially verified against ref-agents source on
Julia 1.12.5 (eval-safe ✓, append-only-safe ✓, all 5 cited token-path bugs real).

ADR 0006:
- (A) plain species (Float64 column, a priori, ADR-0004 append rules) vs
  structured species (a KIND whose INSTANCES are live AA agents; its state.u
  column is a derived reflected count, not storage).
- (B) token TYPE/CONSTRUCTOR/protocol-overrides are HOST JULIA CODE compiled in
  the user's package, never serialized; JSON references a kind/function BY NAME
  against a host-populated registry — categorically distinct from the ADR-0005
  file-eval RCE.
- (C) a per-network function/kind REGISTRY replaces @register. Explains WHY
  @register had to @eval into RD's module (the compiled closure's call-head is
  never substituted, compilers.jl:28, and the closure evals in-module,
  compilers.jl:122), and how registry-by-name dispatch dissolves it (also
  removing the invokelatest world-age indirection, state.jl:68).
- (D) live instantiation lifecycle (instantiate/bind/move/unbind/retire) is
  append-only-safe via entangle!/disentangle! — never a state.u reindex, so it
  cannot break the ADR-0004 frozen varmap.
- (E) a deterministic query API on AA primitives with a (species,name,uuid)
  total order, fixing the D4 violation from raw Dict-value iteration
  (inners::Dict, AA interface.jl:12).
- (F) disposition of @register/@structured_token.
- North-star: BD projects as ProjectToken tokens + the acquisition lever via
  add_structured_token! at a tick boundary (not the no-op event channel).

Contract: new §9 (Structured Tokens & Queries) + §6.10 augmented with the
derived-count and host-Julia-boundary points. PHASE0_REVIEW updated to §1–§9 /
ADRs 0001–0006; resource-retirement and custom-function open questions resolved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Maintainer accepted all four remaining scoping decisions; recorded in the ADRs
and contract, and Phase 0 marked signed off.

- ADR 0003: Phase-3 to_acset interop adapter DROPPED/deferred (off-roadmap,
  impossible for the running engine; reversible later). Back-compat=none and
  registered-removal also folded into a Resolved section.
- ADR 0006: token determinism source = per-species creation counter (not the
  AA uuid); retired-token growth accepted for Milestone 1 with disentangle! as
  the escape hatch. Removed the now-resolved open questions (+ a dup line).
- CONTRACT §2.8: genesis default = poisson at the engine level (backward-
  compatible), flow as the BD/pipeline template/authoring default for routing;
  §6.6 genesis row annotated.
- PHASE0_REVIEW: §5 calls resolved, banner + 5a marked SIGNED OFF 2026-06-20,
  provenance updated.

Phase 0 (contract + ADRs 0001-0006 + semantic test suite) is complete and
signed off; no src/ changes were made. Phase 1 implementation may begin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ct §10/§11/§9.5

Additive increment answering the maintainer's business-process modeling asks
(compact/compositional definition, substitutable granularity, an interface
contract incl. agentic initial state, and filtration-based token selection).
No src/ changes; all produce a plain ModelSpec, disturbing none of the
signed-off §1-§9 semantics.

- ADR 0007 / §10: interface & initial-state contract — three-phase lifecycle
  (authoring→construction→live), declarative serializable initial marking
  population[] (closes the §8.2 S2 reproducibility hole for structured runs),
  and state dump/restore (a superset of the initial-marking schema).
- ADR 0008 / §9.5: agentic species under a filtration — TokenPredicate{kind,
  clauses} + @select, generalizing the kind-only bind to a 𝓕ₜ-measurable
  predicate; unifies phase-as-species / phase-as-attribute.
- ADR 0009 / §11: refinement & open-port composition — refine/abstract via the
  §7.4/J7 FK-splice (plug-compatible granularity substitution), @pipeline /
  @process / @compose compact authoring; closes the §7 J4/J9 join bugs.

Read-verified against ref-agents (Julia 1.12.5); proposed pending sign-off and
a running-engine verification pass. CONTRACT Status, PHASE0_REVIEW (new §8
table), and adr/README updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…write

Maintainer ruling: a structured token's lifecycle state (pipeline phase,
status) is a TOKEN ATTRIBUTE, not a species KIND. ADR 0008 §D / CONTRACT §9.5
revised so phase-as-attribute is THE structured-token model — one Project kind
with a phase field, selected by @select and advanced by @advance — and the
per-KIND-per-phase style is retained only as the degenerate species-field
predicate (@Move back-compat), not a co-equal alternative.

Advancing state is now a field write: new SetField{field,value} action
statement (+ Field{name} ExprNode leaf), applied in finish! over the firing
instance's bound tokens, reusing @Move's set_species! site. @advance(phase,…)
⇒ SetField{:phase,…}; @Move is redefined as its degenerate SetField{:species,…}
sugar (redundant re-entangle! dropped). SetField is legal only in a transition
post-action (a standalone Rule has no bound token); the reconciled canonical
action type family {SetSpecies,SetParams,SetField,AddToken,Activate,Deactivate,
Log,Seq} is defined once in ADR 0010 §C and referenced from ADR 0008/§9.5.

Reconciles with the parallel ADR 0010 (rules/conditional transitions) and
MVP_BD_DEMO.md; updates PHASE0_REVIEW, README, and the BD-demo phase-model note.
ADR 0010 and MVP_BD_DEMO.md remain untracked (owned by that workstream); my
SetField reconciliation edits to ADR 0010 sit in the working tree for it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the Business-Development demo spec (docs/MVP_BD_DEMO.md): an
acquisition-impact-on-pipeline-portfolio MVP written against the
contract as both a demo plan and an adversarial exercise to surface
contract weak-points before Phase-1. Catalogues findings A-I; the
Phase-0.5 increment resolves B/C/F/H, leaving A/D/D-bis/G open and
raising new finding I (token-field-write as a Rule action verb).

Add ADR 0010 (rules/triggers & conditional transitions): the
endogenous, state-contingent decision channel. Repairs the no-op event
channel (solvers.jl:323), adds a stateless transition `guard` AND-ed
with the latching transActivated gate, extends the closed action set to
{SetSpecies,SetParams,AddToken,Activate,Deactivate,Log,Seq}, and adds
fire_mode {every_tick,once}. Maps to CONTRACT §12. Maintainer-ratified
(three forks, 2026-06-21).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…al-code Invoke)

Extend the endogenous action family so callbacks/rules can write a
@select-ed token population and run arbitrary host code, both eval-free:

- SetTokens{predicate, assigns}: population-level token-field write over
  an ADR-0008 TokenPredicate, iterated in the (species, creation_index)
  order. Legal in a Rule (carries its own predicate). Resolves MVP
  finding I.
- Invoke{fn, args}: the general-code escape hatch. Lowers to
  registry[fn](state, transition, args) — the ADR-0006 §C registry-by-
  name boundary in statement position. The file carries only a name; the
  body is host Julia, so CONTRACT §8.4 S4 (no eval, no RCE) holds
  verbatim. Author obligations O1–O4 (determinism/append-only/tick-
  boundary/purity) cover what validate can't statically prove.

Canonical action family is now {SetSpecies, SetParams, SetField,
SetTokens, AddToken, Activate, Deactivate, Invoke, Log, Seq}. Amends
CONTRACT §12.3 + §8.4, ADR index, ADR 0010 open-question, ADR 0005
open-question; re-statuses MVP finding I as resolved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…wired external coupling)

Answers the Workstream-E ask: a reactive network as a node in the AA hierarchy,
querying external hierarchy state from within RD dynamics. Maps to CONTRACT §13.

Grounded in the AlgebraicAgents source (~/.julia/packages/AlgebraicAgents/ovDs5):
RD's ReactionNetworkProblem is ALREADY an @AAgent implementing _step!/_reinit!/
_projected_to, so entangle!(parent, rd) makes it a hierarchy node and AA's
least-projected-time gate coordinates heterogeneous clocks for free. What was
missing is the read/coupling surface, specified here in two directions:

- OUTBOUND: implement getobservable/observables/_getparameters/_setparameters!
  on RD so the hierarchy and AA wires can read species/observables/TokenAgg
  aggregates/params (subsumes the ADR 0006 §E getobservable TODO; resolves MVP
  finding G).
- INBOUND (maintainer ruling: wires + ExternalRef, not a registry walk): a
  top-level inputs[] of declared read-ports + a new closed ExternalRef{port}
  ExprNode leaf usable in any rate/guard/TokenPredicate.value; host wiring via
  add_wire! keeps foreign-agent topology out of the inert RD document.

Determinism pin: external inputs are latched once per tick at AA's _prestep!
hook (prewalk'd before any _step!), because AA steps siblings in raw Dict order
— a live mid-step cross-agent read would violate §4 D4. This is explicit/Jacobi
co-simulation: one-tick coupling lag, no algebraic loop, reproducible under
(hierarchy, seed). The registry/Invoke walk (ADR 0011) is retained as the escape
hatch for reads exceeding a single wired port.

Adds CONTRACT §13 + ExternalRef to the ADR 0005 IR; updates PHASE0_REVIEW tables
and the ADR index (Workstream E now specified). Proposed pending sign-off;
read-verified against ref-agents and the AlgebraicAgents source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…g fixes

Threads a state-owned AbstractRNG through every draw in the step loop so a
(model, seed) pair is bit-reproducible, isolated from the global RNG, and
replayable after reinit! — the determinism foundation the BD counterfactual
and ensemble re-runs depend on. Also fixes the demo-corrupting bugs that broke
the conserved-mass ledger and structured-token binding.

Engine (src/):
- ReactionNetworkProblem grows rng/seed/initial_rng fields; a `seed=` kwarg
  fixes the Xoshiro stream (any Integer accepted, e.g. hash((root,k)) ensemble
  keys), absent it an entropy seed is drawn and stored so the run is still
  replayable (D5/D6). initial_rng snapshots t=0 so _reinit! restores the exact
  stream (D7). Grep-invariant: every rand() in src/ now routes through state.rng.
- finish! lifetime-prune: keep only instances still genuinely in-flight (mirror
  the terminal test); timed-out instances were retained and re-emitted/re-credited
  every tick, inflating conserved mass (§3.4 INV2/INV6).
- free_blocked_species!: undefined `q` -> trans.q (the :nonblock release path).
- token binding: set_bound_transition! was passed the .bound_transition field
  instead of the agent at evolve!/finish! (×3); delete! -> deleteat!.
- add_to_spawn!: findfirst over a range not a scalar; defer into :transToSpawn
  (was incrementing :transHash), so capacity overflow is carried forward (INV3).
- resample!: range-less observable writes .sampled = missing (struct has no .val).
- @mode: :specModality (was a bare symbol -> UndefVarError).

Tests (test/semantic/): flip the now-fixed @test_broken pins to @test and
un-skip the §4 D1/D2/D7/D8 acceptance blocks (verified passing); migrate the
Random.seed!-for-determinism idiom to the seed= kwarg now that runs are
RNG-isolated; fix a const-on-local syntax error that blocked the first file
from loading on Julia 1.12. Suite is green-when-expected: 128 pass / 19 broken
(all genuinely-unbuilt later-stage items) / 0 fail / 0 error. The event-channel
pins stay broken (Stage B replaces the no-op channel with the Rule system).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d (ADR 0010/0011, §12)

Repairs the no-op event channel into the in-model decision channel the BD demo needs:
acquire/inject-capital/toggle-a-line as guarded rules that fire on conditions
(scheduled or state-contingent), serializable and reproducible — MVP finding B.

New (src/actions.jl):
- The closed action family {SetSpecies(set|inc), SetParams, SetField, SetTokens,
  AddToken, Activate, Deactivate, Invoke, Log, Seq} + a RawExpr bridge for legacy
  event actions, dispatched by apply_action!. Action VALUES are raw Expr/literals
  evaluated through the existing RNG-seeded context_eval/wrap_fun closure path —
  no new evaluator (the typed-ExprNode/JSON IR is Stage E).
- Rule{id, guard, action, fire_mode∈{every_tick,once}, enabled}; fire_rules! fires
  every enabled rule at _step! step 10 — Bool guard fires once, numeric guard v fires
  rand(rng, Poisson(v)) times (seeded). `once` rules latch off after firing and are
  re-armed by _reinit! (§4 D7).
- activate!/deactivate!/set_guard! (the missing soft-gate + guard helpers, ADR 0004/0010).
  SetTokens/Invoke land their evaluators with Stage C/E (registry-by-name, eval-free).

Engine wiring:
- ReactionNetworkProblem gains rules::Vector + registry::Dict fields and a `rules=`/
  `registry=` kwarg; :E rows are lifted to Rules at construction (trigger->guard,
  action->RawExpr). fire_rules! replaces the dead event_action! at step 10.
- Transition stateless guard: a :transGuard recipe column (default `true`, compiled
  away) AND-ed with the latching transActivated gate in sample_transitions!. A gated-off
  transition makes NO genesis proposal so it never competes for resources (ADR 0002).
- Fixes a latent alignment bug the guard exposed: sample_transitions! used to SKIP
  (not push) non-firing transitions, compacting state.transitions out of alignment with
  the :T part-index that evolve!/get_allocs! index by. Now every transition is pushed
  with a transFiring flag and evolve! zeroes the genesis quantity of non-firing ones —
  behavior-preserving for existing models, and correct under deactivate!/guards.

Tests: new test/semantic/rules_decisions.jl (20 tests) — once/every_tick rules, capital
injection, transition guards gating genesis, Activate/Deactivate soft gates, Seq+SetParams
composite lever, numeric-guard Poisson multiplicity, reinit latch reset, determinism. The
event-channel @test_broken pins in modality_genesis/determinism flip to positive acceptance
tests (Invariant 7 met). Suite: 152 pass / 17 broken (genuinely-unbuilt later-stage items) /
0 fail / 0 error; tutorials regress clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…/SetField (ADR 0008, §9.5)

Adds the demo's phase-pipeline pillar: select an agentic token by an 𝓕ₜ-measurable
predicate (not kind alone) and advance its lifecycle by a field write. Phase is an
ATTRIBUTE (maintainer-canonical) — one Project kind with a `phase` field, no per-phase
kinds. Answers maintainer Q4 ("take a project in a given state subject to a filtration").

New (src/predicates.jl):
- TokenPredicate{kind, clauses} + Clause{field, op, value} + PRED_OP_WHITELIST
  (==, !=, <, <=, >, >=, in). matches(pred, token, state, transition) evaluates each
  clause's value through the seeded closure path (QuoteNode literals normalized) and
  compares against the guarded token-field read — 𝓕ₜ-measurable, kind-gated. A `nothing`
  predicate matches any token of kind (degenerate = today's bind, backward-compatible).
- select_tokens + token_sortkey: the (species, creation_index) total order (ADR 0006 §E)
  for population-level writes (SetTokens). eval_with_token + @field(name) substitution so
  a SetField/@advance value can read the bound token's own current fields.

Engine wiring:
- UnfoldedReactant/FoldedReactant carry an optional `predicate`; @select(Kind, clauses)
  parses to a TokenPredicate (parse_token_predicate, conjunctive && only). Both structured
  bind sites (genesis + ongoing, solvers.jl) gain `&& matches(predicate, …)` in front of
  the unchanged priority sort + integer take — zero new allocate/order machinery (ADR 0008 §B).
- @advance(field, value) lowers to a bound-token field write in structured_rhs (the
  phase-as-attribute generalization of @Move's set_species!); keeps the token's
  identity/kind/uuid/creation_index/past_bonds, then releases it. No bound token ⇒ silent
  no-op (predicate matched nothing); finish! skips the nothing return. @Move stays as the
  degenerate species-field sugar.
- ReactionNetworkProblem gains creation_counters + creation_index; add_structured_token!
  assigns each token a per-species monotonic creation index (deterministic selection order);
  _reinit! clears them (§4 D7).

Tests: new test/semantic/token_filtration.jl (14 tests) — matches() by ==/>/continuous/
kind-gate, the @select(Phase2)-->@advance(phase,:Phase3) pipeline, identity preservation,
continuous-clause subset selection, determinism, degenerate backward-compat. Suite:
166 pass / 17 broken (genuinely-unbuilt later-stage items) / 0 fail / 0 error; tutorials clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two corrections found while building the BD demo:

- Bind selection among equal-priority tokens was non-deterministic: the structured
  bind sort used `by=priority, rev=true`, so ties (the default priority=0 case) fell
  back to AlgebraicAgents Dict / random token-name order — WHICH equal-priority token a
  transition bound (and thus which program advanced/launched) varied across runs of the
  same (model, seed). Sort by `(-priority, token_sortkey(state, a))` instead, applying
  the ADR 0008 invariant-3 (priority desc, creation_index) total order at both bind sites.
  A run is now reproducible down to token identity, not just aggregate counts.
- `_eval_value` (action value eval) now normalizes a bare QuoteNode to its symbol, so a
  literal `:Phase2` in an AddToken/SetParams field evaluates to `:Phase2` rather than the
  QuoteNode (same fix already applied on the predicate/@advance paths).

Adds a determinism regression test to token_filtration.jl pinning that equal-priority
tokens bind in creation_index order regardless of seed/insertion. Suite stays
166→167 pass (the new test) / 17 broken / 0 fail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
thevolatilebit and others added 18 commits July 18, 2026 04:06
…ives, decision case studies

Migrate the remaining six demos in place (charter §4 anti-drift rule) into executable
Literate sources, and wire the full site nav in make.jl. Every page executes end-to-end at
build time (seed-pinned, reproducible) and closes on / leads with a computed decision-relevant
quantity in a computational register — no internal-QA meta in reader prose (charter §2/§12).

Tutorials (learning tier, cumulative buildup like the A1 exemplar):
- A2 advanced (agentic_pipeline + core §3–4 + introspection §4): structured tokens, phase-as-
  attribute @select/@advance, the three modalities + priority allocator under contention, the
  in-model decision Rule. Closes on a Series-B treatment_effect: +0.52 launches ± 0.24 SE.
- A3 expert (aa_integration + introspection §7 + agentic §6–7): model-is-data JSON round-trip,
  AA coupling with wires both directions, checkpoint/restore, the exec-map hero visual. Closes
  on the finance sibling reconstructing RD's cash off a wire (one-tick Jacobi lag, verified).
- A4 deep-dive serialization (agentic §4/§6): the eval-free JSON document, validate clean vs
  broken → Diagnostic, the RCE boundary (hostile string loads inert), the ExprNode IR. Closes
  on byte-identical DSL-vs-reload trajectories.
- A5 deep-dive composition (refinement_tour + core §7): the granularity ladder @join/@equalize/
  @process/@port/@compose/@pipeline/refine/abstract + refinement_diagnostics; authoring-time-only
  invariant. Closes on the granularity-substitution guarantee (Σct/ΠPoS match).

Case studies (understanding tier, question-titled, headline-number-first):
- B1 flagship "What is the marginal eNPV of the Nth scientist?" — shadow price of the binding
  resource: the 5th scientist worth +$19M ± $2.5M; exec map paints the binding pool.
- B2 "What is this in-licensing asset worth to this pipeline?" (from bd_acquisition) — attributable
  Δ-rNPV +1947 ± 367, rNPV not additive under contention (cash binds, synergies super-additive).
- B3 "When should you kill a program?" — value-maximizing kill threshold θ*=0.5 worth +48.5 ± 12.6;
  the endogenous decision channel (kill rule in the model, state-contingent, serializable).

The full site builds green (docs/make.jl: Documenter HTML + Literate pre-pass over all eight
sources; reference/case-study/deep-dive nav wired). Generated deep_dives/*.md is gitignored like
the tutorials/case_studies markdown — the Literate .jl is the tracked source of truth. Every
source passes Runic. The A2 migration also surfaced a stale demo assumption: the @Rate cycletime=0
combination is now rejected at construction (the validator hard-errors), reshaped accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ite builds green

Update the tracker (§10) and per-facet status (§1, §5, §6, §7, §9, §11) to reflect the
landed work: all four remaining tutorial tiers/deep-dives (A2 advanced, A3 expert, A4
serialization, A5 composition), all three decision case studies (B1 flagship marginal-scientist,
B2 in-licensing, B3 kill-a-program), and the capability-organized API reference + JSON-schema
page (C1/C2) are rendered and the full Documenter site builds green. Each facet's status now
records its computed headline number. Remaining: Workstream D (explanation layer + arXiv paper)
and E3/E4 (refined case-study HTML + paper build).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The bare difficulty adjectives "Introductory / Advanced / Expert" read as terse level
tags rather than saying what each page teaches. Append the subject to each, echoing the
page H1s, so the sidebar reads as a learning path: "Introductory: your first model",
"Advanced: structured-token portfolios", "Expert: coupled heterogeneous systems". The two
deep-dives already name their topic and are unchanged. Site rebuilds green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…heme, concept figures

Establish a unified visual language for the docs site and README, drawn from the
Claude Design identity board (spec/design_system.html) and the bd_acquisition
presentation's International Typographic Style (ink + one azure accent + semantic
figure hues: teal=people, amber=money, rose=risk).

- rd-theme.css: a thin override layer over Documenter 1.x (light + dark). Repaints
  the navigation sidebar to the ink-black masthead field (mono labels, azure
  section eyebrows, azure active-page rule), gives h1 an azure underline accent and
  h2 a hairline rule, recolours admonitions to the semantic palette, and flattens
  code blocks. Wired via assets=[...] in make.jl; azure stays the single accent
  (heading anchors kept ink).
- Logo — "the firing glyph" (place → azure transition bar → place) extracted from
  the Claude Design board as real assets: logo.svg/logo-dark.svg (reversed-out for
  the black sidebar, both themes), logo-mark.svg (full-colour on white),
  logo-wordmark.svg (README lockup), favicon.svg (16px-legible square).
- Concept figures lifted from demo/bd_acquisition/presentation.html into shared,
  self-contained SVGs (font-fallback via CSS attribute selectors so they read via
  <img>): petri-anatomy, single-firing, token-kinds. Placed in index.md ("@raw
  html" figures) and the README hero.

Site builds green from source (exit 0); docs/build is gitignored so only sources
are committed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per house review: the single accent moves from azure (#0A47FF) to the house teal
(#0A8A84), and the navigation sidebar drops the pitch-black field for a warm
off-white panel (light) / warm-dark panel (dark).

- rd-theme.css: --rd-accent family repointed to teal (base #0A8A84, hover #076A65,
  on-dark #3FB5AF). Sidebar rewritten theme-adaptively — light off-white (#EDEDEA)
  with ink labels + teal eyebrows + teal active-rule; dark warm-near-black
  (#1B1B19) twin. All links, the h1 underline, admonitions, and the active-page
  marker follow the accent var automatically.
- Logos: transition bar (and wordmark ".jl") azure→teal across logo-mark,
  logo-wordmark, favicon. The sidebar logo now flips by theme — logo.svg is the
  INK mark (light off-white nav), logo-dark.svg the reversed-out mark (dark nav);
  both carry the teal bar.
- Concept figures (petri-anatomy, single-firing, token-kinds): the events/advance/
  @select azure recoloured to teal, unifying the figure vocabulary with the mark.

Site builds green from source (exit 0); light mode verified via headless render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two theming regressions from the earlier passes:

- Active-section sub-anchors in the sidebar kept the stock theme's white fill
  (`li.is-active .tocitem{background:#fff}` — a DESCENDANT rule my child-combinator
  override missed), which clashed with the off-white panel. Now cleared to
  transparent and re-specified under `li.is-active … ul.internal` so it out-weighs
  the stock rule; hover twins too.
- Code blocks stayed white in dark mode: the unscoped `pre{background:#fff}`
  (id+class specificity) beat the stock `html.theme--documenter-dark pre`, so light
  syntax-highlight text rendered on white — unreadable. The dark-scoped `pre` now
  restores a dark field (--rd-code-bg-dk #282F2F, == stock dark) + light text.

Verified in fresh builds: light nav (active sub-items on the off-white tint, no
white boxes) and a real dark-mode render (code on a dark field, teal keywords).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Documenter ships six themes and offers all of them via the theme picker; our brand
layer (assets/rd-theme.css) only repaints documenter-light + .theme--documenter-dark,
so the four catppuccin flavours rendered correct but UN-branded.

There is no `HTML(; themes=…)` kwarg in Documenter 1.x — the picker, the copied CSS,
and the <head> links all read the hardcoded `HTMLWriter.THEMES` vector. It is a
mutable Vector shared by every path, so filter it in place before makedocs to keep
only the two we style. Verified: build emits only documenter-{light,dark}.css, the
<head> links and picker options list just those two (+auto), catppuccin dropped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… docs

Replace the stale, hedge-heavy README (old horizontal nav bar, "coming in a
follow-up pass" notes, removed SIR/Catlab framing, dead diagram2/diagram3.png
images) with the conventional Julia-package outline and the new teal design
language.

- Hero: teal wordmark lockup + docs/license/Julia badges + the petri-anatomy
  figure (drops the old wiring/attributes PNGs).
- Adds Installation and a runnable Quick start (the SIR snippet, verified against
  the executed introductory tutorial — `using ReactiveDynamics` re-exports
  `simulate`).
- "The core idea" folds the transition/modality/structured-token prose into two
  tight bullets + the token-kinds figure.
- New "What it's for" section leads with the three case-study headline numbers
  (5th scientist ≈ +$19M; contextual in-licensing value; interior kill threshold),
  linking the published pages.
- Documentation section points at the deployed site (tutorials/case studies/
  reference) with spec/ as the normative record; Demos list unchanged but tidied;
  keeps the DyVE context and adds an explicit MIT license line.

All docs-site links map to real generated pages; figures reuse the committed
shared SVGs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove the bottom License section (MIT is already declared via the header badge
  + LICENSE file).
- Add a Contributing section modeled on AlgebraicAgents.jl's: points contributors
  at the spec/ design records (STATUS, CONTRACT, ADRs, INVENTORY.md), the ADR
  "add-don't-rewrite" + code-is-truth conventions, and the test/format steps; the
  two contribution forms (PRs; issues/enhancements) mirror the AA wording against
  this repo's issue tracker. This also gives INVENTORY.md its first user-facing
  reference from the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GitHub surfaces a root CONTRIBUTING.md in the PR/issue UI, so move the guidance out
of the readme into its own file (modeled on AlgebraicAgents.jl's, retargeted to
this repo: spec/ design records as the starting point, the append-only-ADR +
code-is-truth conventions, and the Pkg.test()/Runic steps).

Readme now just points to CONTRIBUTING.md, and re-adds a short License section
linking LICENSE + LICENSES_THIRD_PARTY (both files referenced rather than inlined).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
INVENTORY.md is a durable engineering artifact (the current-source map), which
CLAUDE.md's "where the durable spec lives" section groups under spec/ — it sat at
root by historical accident, the only such artifact not already there. Move it
beside its companion STATUS.md (state index ↔ source map) via `git mv`.

Reference updates: the live links were mostly inside spec/ reaching up with
../ / ../.. — those now shorten (STATUS/DOCS_CHARTER → INVENTORY.md sibling;
adr/README + adr/0001 → ../INVENTORY.md). Root files gain the spec/ prefix
(CLAUDE.md, CONTRIBUTING.md). All five markdown links verified to resolve to
spec/INVENTORY.md. Bare-prose mentions (test/ comments, adr/0001 §consequences,
adr/0015 stale-doc line-refs) carry no path and are left as-is; the frozen
spec/history/ handoff snapshots are intentionally untouched (append-only history),
so their ../../INVENTORY.md links now point at the pre-move path by design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce the standard Julia-package CI suite under .github/:

- CI.yml: version×OS test matrix (1.12 min + 1 latest on ubuntu/macOS/
  windows), an allow-fail nightly job, a downgrade-compat job guarding the
  [compat] lower bounds, Codecov upload, and a Documenter build+deploy that
  installs Graphviz (the tutorials render exec-maps through dot) and runs
  doctests.
- Runic.yml: formatting check via fredrikekre/runic-action.
- TagBot.yml: releases on registry tagging.
- CompatHelper.yml: raises [compat] upper bounds across ., test, docs.
- dependabot.yml: keeps the workflow action versions current.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "where the durable spec lives" file list was duplicated between CLAUDE.md and
spec/STATUS.md and had already drifted (both listed INVENTORY.md at root until the
recent move). Make STATUS.md the single authoritative catalog; CLAUDE.md now keeps
only the spec/docs split rule and the ADR append-only convention, names the three
files an agent touches most (STATUS/CONTRACT/INVENTORY, tagged with what each
answers — state / what-it-must-do / where-it-lives), and points at STATUS.md for
the full list. Nothing dropped: STATUS.md's catalog is the superset (PR_DRAFT,
DOCS_CHARTER, the BD demo doc, retired/archived docs all covered).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refine the Documenter site's brand layer and lock in the identity board's
teal accent across the docs theme and the design-system source.

rd-theme.css:
- Swap the ONE accent from azure to teal (#0A8A84) throughout — links,
  h1 underline, admonitions, code keywords, active-page marker.
- Sidebar: near-white panel (#F3F3F1) that reads as a distinct panel vs the
  white content ground (was #FBFBFA, ~1pt off #FFFFFF, so the logo floated);
  the active menu group is boxed (hairline top+bottom + teal left rule),
  matching the stock/Catppuccin active-group look.
- Brand lockup: firing-glyph mark capped at 4.5rem; wordmark set in the Inter
  display face (was mono) with its ".jl" extension tinted teal.
- Fix a cross-theme specificity trap: stock rules for .docs-logo>img
  (max-height) and .docs-package-name (font-size) are unscoped on
  documenter-light but PREFIXED with html.theme--… on documenter-dark and
  catppuccin-latte, so an unscoped override only won on light — the logo and
  wordmark rendered larger on the other two. Repeat both rules under every
  shipped theme prefix so the lockup is uniform across all three.

rd-logo.js (new): splits the trailing ".jl" off Documenter's bare sitename
text node into <span class="rd-jl"> so rd-theme.css can tint it. Idempotent;
bails unless the anchor is a single ".jl"-suffixed text node.

make.jl: offer catppuccin-latte in the theme picker (drop the three darker
flavours); set the theme order explicitly. Keep the branded warm
documenter-light as the default — it coheres with the warm-neutral identity
system; catppuccin's cool blue-grey ground fights the teal/amber/rose hues.

design_system.html: swap the identity board's accent azure -> teal to match
the docs theme; re-point the freed palette slot to brand rose so the four
swatches stay distinct.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump the .docs-package-name font-size from 1.1rem to 1.4rem so the wordmark
better balances the 4.5rem firing-glyph mark above it, while keeping the ".jl"
clear of the sidebar's right edge. Applied under all three theme prefixes so
it stays uniform across documenter-light/dark and catppuccin-latte.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r papers

The web explanation quadrant (D1 contract-as-explanation pages, D2 ADR
reading surface) is dropped: re-hosting the normative contract and ADRs
as Documenter pages is a third copy that drifts from the spec/ source
for no reader payoff the papers don't cover. The site now links directly
to spec/CONTRACT_DRAFT.md and spec/adr/ on GitHub.

The single arXiv paper is split into two peer papers under paper/:
- D3a — methodology paper (mechanism-first, arXiv, technical)
- D3b — HBR-style adoption/value paper for non-technical executives that
  builds motivation → solution → some examples (not use-case-first)

Retarget the in-text explanation/*.md links that would now dangle
(composition, serialization, case studies, json_schema, index) to the
normative spec/ artifacts, and drop the commented-out Explanation nav
stub + header notes from make.jl. Sync STATUS.md's one-line description.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ocs site

Fold the polished draft (previously the untracked docs/PR_DRAFT.md, which
used stale docs/-prefixed spec paths) into the canonical spec/PR_DRAFT.md
and remove the stray duplicate so the two can't drift.

- Fix stale paths: docs/CONTRACT_DRAFT.md → spec/, docs/adr/ → spec/adr/,
  docs/STATUS.md → spec/STATUS.md, docs/history/ → spec/history/.
- Rewrite the "Documentation and tutorials" section: docs are no longer
  deferred to a follow-up — the docs-tutorials work is consolidated into
  rework. Summarize the Diátaxis structure (tutorials, applied case
  studies, API reference) and the two-paper "why" surface.
- Truth up Tests (801 pass, CI scaffold added) and the draft banner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removes the audience-facing presentation layer (presentation.html,
BRIEF.md, build_presentation.jl, the handoff bundle, and the committed
figure PNGs) and fixes the now-stale references in the demo README and
the docs charter. The runnable engine core stays: run_demo.jl, host.jl,
model.rdj.json, analysis.jl, export_data.jl, figures.jl. figures.jl and
export_data.jl regenerate their outputs locally (now gitignored).
@thevolatilebit
thevolatilebit requested a review from slwu89 July 21, 2026 16:55
@thevolatilebit thevolatilebit self-assigned this Jul 21, 2026
@thevolatilebit
thevolatilebit marked this pull request as ready for review July 21, 2026 22:58
thevolatilebit added a commit that referenced this pull request Jul 22, 2026
Lands the workflow set the engine rework introduced (CI.yml test matrix
+ docs, Runic.yml formatter, refreshed CompatHelper/TagBot) and drops the
superseded Tests.yml/Formatter.yml/Documenter.yml. Until these live on
the default branch, Actions does not recognize them, so PRs targeting
main (e.g. #17) report no checks. Merging this registers them.
thevolatilebit and others added 7 commits July 22, 2026 02:40
Empty commit to emit a pull_request synchronize event. The CI/Runic
workflows were registered on the default branch in #18, but an already-open
PR gets no event when its base branch changes — only on commits to its own
head. This nudges the checks to run.
Main carried a small 2024 readme edit that conflicted with the rework's
full readme rewrite, leaving PR #17 unmergeable — and an unmergeable PR
produces no merge ref, so no pull_request checks run. Resolve readme.md
in favor of the rework version. Main's only other delta (the CI workflows
from #18) is already identical here, so this merge is otherwise a no-op.
deploydocs declined to deploy on PR builds because push_preview was
unset (its deploy-criteria check printed "✘ push_preview ... is true",
DOCUMENTER_KEY ✔). Enabling it publishes a per-PR preview under
gh-pages previews/PR<n>/ on each Documentation run; the live dev/ site
still deploys only on push to the default branch.
The preview deploy failed with "Permission denied (publickey)" — the
DOCUMENTER_KEY secret (2023) had no matching deploy key on the repo. A
fresh read/write deploy key + secret are now installed; empty commit to
re-run the Documentation job and confirm the preview pushes to gh-pages.
The `docs-dev` badge served the Jan-2024 site: /dev/ is written only by
pushes to `main` (Documenter devbranch), and the rework lives on the
`rework` branch (PR #17), never merged to `main`. Point the badge at the
PR #17 preview build as a stopgap; revert to /dev/ once PR #17 merges and
CI deploys /dev/ from main.

Bump version 0.2.9 -> 0.3.0 (minor: the rework retired the old ACSets/
@ReactionNetwork surface). No release/registration is triggered by this.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The favicon carried a hardcoded white 48×48 rect while the three sibling
logo assets (logo.svg / logo-dark.svg / logo-mark.svg) are all transparent.
Remove it so the tab bar shows through, matching the rest of the mark set.
Duotone (near-black places + teal bar) is kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two CI checks on PR #17 were red/noisy; both are now clean.

Downgrade compat (julia-downgrade-compat runs `alldeps`: pins every dep AND
weakdep to its [compat] floor, then buildpkg+runtest on Julia 1.12):
  - drop unused StatsFuns from [deps]/[compat] (its 1.5 floor also conflicted
    with the Distributions 0.25 floor);
  - raise eight stale floors that predated the Julia 1.12 requirement and
    pinned patch-0 releases that no longer precompile/load on 1.12
    (CSV, JLD2, MacroTools, ComponentArrays, JSON, Distributions, and the
    Plots/Arrow weakdeps) to the versions the green suite resolves against.
Verified by emulating the exact alldeps floor-pinning: resolve + precompile
on 1.12 and the full suite passes 801/801.

Documentation warnings:
  - checkdocs = :exports so the ~80 internal (unexported) helper docstrings are
    no longer reported as "not included" (was 86 warnings → 0);
  - resolve every "Cannot resolve @ref" (6 → 0) by surfacing the referenced
    symbols in @docs blocks (Transition/ReactantSpec, prepend!/prepend_obs!/
    normalize_name, ProgramLedger + accessors, observables/getobservable, the
    solution-export macros, ReactionNetwork, validate) and promoting the
    ReactionNetwork/validate comment blocks to real docstrings;
  - raise the page size_threshold/size_threshold_warn above the largest
    generated page so no page-size violation is reported.
The lone remaining docs warning is informational: a Plots figure advertises a
fat text/html rep alongside SVG, so the low example_size_threshold makes
Documenter externalize the large figure SVGs (keeping page HTML lean) and log
that it used the image fallback. Raising the threshold would inline the fat
rep and bloat pages, so it is left low with the rationale documented in make.jl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants