runward 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -56,6 +56,7 @@ export async function checkCommand(opts) {
56
56
  if (opts.strict) {
57
57
  const CONFORMANCE = [
58
58
  { phase: "architect", deliverable: "architecture.md", label: "Architect" },
59
+ { phase: "topology", deliverable: "execution-topology.md", label: "Topology" },
59
60
  { phase: "floor", deliverable: "floor.md", label: "Floor" },
60
61
  { phase: "govern", deliverable: "governance/threat-model.md", label: "Govern" },
61
62
  ];
@@ -94,6 +95,10 @@ export async function checkCommand(opts) {
94
95
  console.log(" " + c.darkGray("ratify each: write the real why + a re-evaluation trigger and set Status: accepted (rename DRAFT→ADR), or remove it. A hypothesis is not a decision."));
95
96
  strictGaps += unratified.length;
96
97
  }
98
+ if (checked > 0 && strictGaps === 0) {
99
+ console.log(section("Semantic check (advisory, above the gate)"));
100
+ console.log(" " + c.darkGray("the gate proved every CRITICAL/HIGH rule was traced — not that the code applies it. Before you cross, run the verify workflow (runward/workflows/verify.md): an adversarial cite-vs-apply pass, ideally on a different model. Advisory, agent-executed, never blocks the gate (ADR-0007)."));
101
+ }
97
102
  }
98
103
  if (opts.coverage) {
99
104
  console.log(section("Documentation coverage (advisory)"));
@@ -1,8 +1,8 @@
1
1
  /** Total number of craft rules shipped under templates/rules/. */
2
- export const EXPECTED_RULES = 54;
2
+ export const EXPECTED_RULES = 58;
3
3
  /** Gate adapters shipped under templates/adapters/ (ADR-0012): one per harness seam, plus the port-contract README. */
4
4
  export const EXPECTED_ADAPTERS = 4;
5
5
  /** Routed-count floor: minimum CRITICAL/HIGH rules mapped to each build phase (ADR-0002).
6
6
  * Lowering a floor is a deliberate, tracked edit — the `phases:` mapping cannot be silently
7
7
  * stripped to make `check --strict` pass vacuously. */
8
- export const EXPECTED_MAPPED = { architect: 6, floor: 10, govern: 10 };
8
+ export const EXPECTED_MAPPED = { architect: 6, topology: 4, floor: 10, govern: 10 };
@@ -13,6 +13,7 @@ export const PHASES = [
13
13
  id: "architect", label: "2 · Architect",
14
14
  artifacts: [
15
15
  { label: "Architecture note", relPath: "architecture.md", templateKey: "architecture.md" },
16
+ { label: "Execution topology", relPath: "execution-topology.md", templateKey: "execution-topology.md" },
16
17
  { label: "Decision matrix", relPath: "decision-matrix.md", templateKey: "decision-matrix.md" },
17
18
  { label: "Decision journal (≥1 ADR)", relPath: "adr" },
18
19
  { label: "Port contracts (≥1 filled)", relPath: "contracts" },
package/dist/lib/paths.js CHANGED
@@ -14,6 +14,7 @@ export const MISSION_LAYOUT = {
14
14
  "mission-contract.md": "mission-contract.md",
15
15
  "reference-stack.md": "reference-stack.md",
16
16
  "shared-bricks.md": "shared-bricks.md",
17
+ "execution-topology.md": "execution-topology.md",
17
18
  "floor.md": "floor.md",
18
19
  "gap-analysis.md": "gap-analysis.md",
19
20
  "adr/ADR-0000-template.md": "adr/ADR-0000-template.md",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runward",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "After the spec: ship and run. A delivery framework for agentic systems — floor first, evolution on evidence, governance from day zero, handover with proof.",
5
5
  "keywords": [
6
6
  "agentic",
@@ -36,8 +36,8 @@
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsc",
39
- "test": "npm run build && node test/smoke.js",
40
- "prepublishOnly": "npm run build && node test/smoke.js"
39
+ "test": "npm run build && node test/smoke.js && node test/oscal-schema.js",
40
+ "prepublishOnly": "npm run build && node test/smoke.js && node test/oscal-schema.js"
41
41
  },
42
42
  "dependencies": {
43
43
  "@inquirer/prompts": "^7.0.0",
@@ -45,7 +45,9 @@
45
45
  "commander": "^14.0.0"
46
46
  },
47
47
  "devDependencies": {
48
- "typescript": "^5.5.0",
49
- "@types/node": "^22.0.0"
48
+ "@types/node": "^22.0.0",
49
+ "ajv": "^8.20.0",
50
+ "ajv-formats": "^3.0.1",
51
+ "typescript": "^5.5.0"
50
52
  }
51
53
  }
@@ -0,0 +1,53 @@
1
+ # Execution Topology
2
+
3
+ > **Usage.** Read this note once the ports are named in `architecture.md`. It is the bridge between the two visions the doctrine governs behind the same ports: the **application domain** (what the system does) and the **execution topology** (where, and under which sovereignty, each port's adapter actually runs). `architecture.md` decides the ports; `shared-bricks.md` decides *where a shared brick lives*; this note records, **port by port**, the placement, the data it carries and the sovereignty it inherits — so an architect reads the domain and its infrastructure incarnation in one table. runward traces and governs this decision; it never deploys, provisions or orchestrates anything.
4
+
5
+ ## The two visions, behind the same ports
6
+
7
+ The doctrine is explicit: *"language and deployment topology are adapter decisions, made behind stable contracts"* (§01), and *"a service is just an adapter that has migrated into its own process; the domain does not change"* (§02). So topology is not a separate domain: it is the **location behind the port**. *"The question is therefore never port or no port: it is which location behind the port… This location trade-off is reversible precisely because the contract itself does not move"* (§15).
8
+
9
+ "Never a runtime" does not mean "ignore topology." The topology decision is a decision — and a traced decision is exactly what the gate verifies. This note is where the decision lives.
10
+
11
+ ## The port → placement map
12
+
13
+ One row per domain port from `architecture.md`. The location family is one of the five in `shared-bricks.md`. Any placement that is not in-app carries an ADR. Every class of data crossing the port carries a declared sovereignty level.
14
+
15
+ | Port | Adapter (what runs) | Location family | Data class(es) crossing it | Sovereignty level | ADR | Re-evaluation trigger |
16
+ |---|---|---|---|---|---|---|
17
+ | _e.g._ Model port | direct SDK, in-process | In-app | public reference | standard | — | a second app needs the same routing/quotas → shared gateway |
18
+ | _e.g._ Persistence port | store adapter | In-app | internal business | raised | — | multi-instance in sight → externalized state |
19
+ | _e.g._ Trace sink | observability adapter | Existing infrastructure | regulated (traces are data) | sovereign / self-hosted | ADR-NNNN | third-party trace export requested → its own ADR |
20
+ | … | … | … | … | … | … | … |
21
+
22
+ **Filling rules (what the gate expects to be traced, not what the answer must be).**
23
+
24
+ - **Placement is named per port.** Each domain port has a location family. The grid is stable; its filling is a dated, context-specific exercise the doctrine leaves to you.
25
+ - **Non-in-app placement has an ADR.** Moving a port's adapter off in-app is a structuring decision — lock it in the infra ADR family (placement, sovereignty, secrets boundary, agent-identity location, multi-region).
26
+ - **Sovereignty is graduated by class of data.** Not a wholesale switch: public reference can live on a managed runtime; regulated or personal data pushes placement toward the internal families. Decide it per data class, at field level, not per system.
27
+ - **Traces are data.** An observability pipeline that ships prompts and tool arguments to a third party is exporting your most sensitive payloads. *"Exporting them to a third-party service is an exfiltration like any other"* (§10/§15): a third-party trace export is either absent or covered by a decision naming recipient, data class and retention.
28
+ - **Secrets live at the network boundary.** The real key is attached by the infrastructure at the network boundary, replaceable without redeploying; the agent side holds a placeholder. Record that vaulting/injection decision.
29
+
30
+ ## Usage registry
31
+
32
+ Risk is classed **by deployment, not by platform** (§15): the same platform hosts a harmless FAQ bot and an agent with write access to payments. Governance attaches to each deployment. This registry is what answers, at audit time, "what runs where, touching what." It is a governed engineering artifact, not a compliance declaration (see ADR-0015/0016 and `runward compliance`).
33
+
34
+ | Deployment | Risk class | Data classes touched | Action scopes | Owner / responsible | Last review |
35
+ |---|---|---|---|---|---|
36
+ | _e.g._ triage-bot / prod | low | public reference | read-only | — | — |
37
+ | _e.g._ payments-agent / prod | high | regulated, personal | mutate (approval-gated) | — | — |
38
+ | … | … | … | … | … | … |
39
+
40
+ ## Rule conformance
41
+
42
+ > Account for every CRITICAL/HIGH craft rule mapped to the topology phase (`runward/rules/`, frontmatter `phases: [topology]`). `applied` needs a pointer; `deviated` needs an ADR; `n/a` needs a reason. `runward check --strict` verifies this table is complete — it checks a traced placement decision, not where you should run.
43
+
44
+ | Rule | Status | Evidence |
45
+ |---|---|---|
46
+ | [rule-slug] | applied \| deviated \| n/a | [pointer, ADR-id, or reason] |
47
+
48
+ ## Cross-references
49
+
50
+ - `architecture.md` — the ports this note places.
51
+ - `shared-bricks.md` — the placement families, the six criteria, the brick matrix, sovereignty by data class.
52
+ - `governance/threat-model.md` — a third party (connector, skill, remote tool) is untrusted input; a shared surface amplifies it.
53
+ - `adr/` — every non-in-app placement, sovereignty tightening and trace-export decision is an ADR.
@@ -0,0 +1,13 @@
1
+ ---
2
+ title: Every Port Has a Named Placement
3
+ impact: HIGH
4
+ phases: [topology]
5
+ impactDescription: Keeps the execution topology traced port by port, so a placement never drifts unseen behind the domain
6
+ tags: [topology, placement, ports, infrastructure]
7
+ ---
8
+
9
+ ## Every Port Has a Named Placement
10
+
11
+ > **The port is the bridge between the two visions.** The domain says what a port does; the topology says where its adapter runs. A port with no named placement is a decision no one made.
12
+
13
+ Every domain port named in `architecture.md` carries, in `execution-topology.md`, a location family: in-app, existing infrastructure, dedicated internal platform, managed infrastructure service, or managed model-vendor runtime. The grid is stable; its filling is a dated, context-specific exercise left to the operator. A placement that is not in-app is a structuring decision: lock it in an ADR. Moving a placement never changes the domain, "a service is just an adapter that has migrated into its own process; the domain does not change", so the row moves and the port does not. runward traces the placement decision; it never deploys.
@@ -0,0 +1,13 @@
1
+ ---
2
+ title: Sovereignty Graduated by Class of Data
3
+ impact: CRITICAL
4
+ phases: [topology]
5
+ impactDescription: Prevents regulated or personal data from silently landing on a placement that cannot hold its sovereignty
6
+ tags: [topology, sovereignty, data, compliance]
7
+ ---
8
+
9
+ ## Sovereignty Graduated by Class of Data
10
+
11
+ > **Sovereignty is not a switch, it is a gradient.** It is decided per class of data crossing a port, not wholesale for a system.
12
+
13
+ For every port, each class of data crossing it carries a declared sovereignty level. Public reference data can live on a managed vendor runtime; internal business data raises the bar; regulated or personal data pushes placement toward the internal families, decided at field level, not system level. Sovereign by default wherever sovereignty costs only operations, gateway, memory, registry, execution, access control; the only tier where it can cost quality is the model, and the model port keeps sensitivity-based routing local and reversible, best available engine for ordinary data, sovereign engine for regulated data.
@@ -0,0 +1,13 @@
1
+ ---
2
+ title: Trace Export Is a Decision, Because Traces Are Data
3
+ impact: HIGH
4
+ phases: [topology]
5
+ impactDescription: Stops the most sensitive payloads leaving under the name "telemetry" without a named, reviewed decision
6
+ tags: [topology, observability, data, exfiltration]
7
+ ---
8
+
9
+ ## Trace Export Is a Decision, Because Traces Are Data
10
+
11
+ > **Execution traces contain the prompts and the outputs: your most sensitive payloads.** Shipping them to a third party under the name "telemetry" is exfiltration unless a decision says otherwise.
12
+
13
+ Any export of execution traces to a third-party service is either absent, or covered by a traced decision naming the recipient, the data class, and the retention, the same review as any data transfer. "Exporting them to a third-party service is an exfiltration like any other." Traces follow the same sovereignty gradient as the data they carry, and the decision lives in `execution-topology.md`, cross-referenced from `governance/observability-schema.md`.
@@ -0,0 +1,13 @@
1
+ ---
2
+ title: A Usage Registry, Because Risk Is Classed by Deployment
3
+ impact: HIGH
4
+ phases: [topology]
5
+ impactDescription: Lets you answer, at audit time, what runs where touching what — risk attaches to a deployment, not a platform
6
+ tags: [topology, governance, registry, compliance]
7
+ ---
8
+
9
+ ## A Usage Registry, Because Risk Is Classed by Deployment
10
+
11
+ > **The same platform hosts a harmless FAQ bot and an agent with write access to payments.** A platform-level risk label would strangle the first or wave the second through.
12
+
13
+ Keep a usage registry: per deployment, the risk class, the data classes touched, the action scopes, and the owner, under an identified responsible party and a periodic review. "Risk is classified per deployment, not per platform": the same foundation reused for a sensitive use tips that deployment into a more demanding regime, without the architecture changing. It is a governed engineering artifact that feeds `runward compliance`, never a compliance declaration in itself.
@@ -12,7 +12,7 @@ This project is delivered with the Runward method: floor first, evolution on evi
12
12
 
13
13
  ## How to work
14
14
 
15
- - Apply the craft rules in `runward/rules/` while building — and confront them at the point of action, not from memory. Each rule declares where it applies (`phases:`); a build phase surfaces its CRITICAL/HIGH rules to open and account for in the deliverable's `Rule conformance` manifest: `applied` with a `file:line` or test, `deviated` with an ADR, `n/a` with a reason. When a rule and a habit conflict, the rule wins; deviating requires an ADR. `runward check --strict` verifies the manifest is complete — it checks that a decision was traced, never the quality of the code; you judge that at the gate.
15
+ - Apply the craft rules in `runward/rules/` while building — and confront them at the point of action, not from memory. Each rule declares where it applies (`phases:`); a build phase surfaces its CRITICAL/HIGH rules to open and account for in the deliverable's `Rule conformance` manifest: `applied` with a `file:line` or test, `deviated` with an ADR, `n/a` with a reason. When a rule and a habit conflict, the rule wins; deviating requires an ADR. `runward check --strict` verifies the manifest is complete — it checks that a decision was traced, never the quality of the code; you judge that at the gate. When the gate is green, run `runward/workflows/verify.md` before crossing: an advisory, adversarial cite-vs-apply pass (ideally on a different model) that judges whether the code an `applied` row points at actually applies the rule or merely cites it. It is advisory — it never blocks the gate (ADR-0007).
16
16
  - Consult `runward/decision-matrix.md` before adding any capability: 22 arbitrations, each with a sober default and an explicit trigger. No trigger, no change.
17
17
  - Before any structural decision, run `runward/workflows/decision-loop.md`: verify in the real code, check the sourced state of the art, challenge the source, take a durable position, lock it in an ADR — only then edit.
18
18
  - One ADR per structural decision, in `runward/adr/`, with a dated re-evaluation trigger. Use the template `runward/adr/ADR-0000-template.md`.
@@ -8,12 +8,14 @@ Use this workflow once framing is decided and structure must follow: "how do we
8
8
 
9
9
  - The framing note: floor/target split, success criterion, hard constraints, presumed boundaries.
10
10
  - The `mission/architecture.md`, `mission/port-contract.md`, and `mission/adr/ADR-0000-template.md` templates.
11
+ - The `mission/shared-bricks.md` and `mission/execution-topology.md` templates (the infrastructure vision).
11
12
 
12
13
  ## Outputs
13
14
 
14
15
  - A light architecture note.
15
16
  - The port list with contracts and the integration protocol.
16
- - One ADR per structuring decision.
17
+ - The execution-topology note: each port placed behind its location family, with data class and sovereignty.
18
+ - One ADR per structuring decision, including each non-in-app placement.
17
19
 
18
20
  ## Procedure
19
21
 
@@ -28,6 +30,8 @@ Use this workflow once framing is decided and structure must follow: "how do we
28
30
 
29
31
  **Name the default topology and its triggers.** A modular hexagonal monolith by default: one deployable, pure domain plus adapters. A single orchestrator directing specialists: it composes, it carries no business logic. A tool registry plus a middleware chain as the single transversal surface (logging, access, cost, approval, traces) — the chain stays thin, the registry stays an index, never a brain. One core language for server and interface, a thin model abstraction (a direct SDK, not a heavy chain framework); polyglot only via a sidecar or service, justified by a mature library or proven performance need. Name each default's evolution trigger here; cross it only in `iterate`.
30
32
 
33
+ **Place the ports — the second vision, behind the same ports.** The application domain says *what* the system does; the execution topology says *where*, and under which sovereignty, each port's adapter runs. They are not two subjects: a placement is an adapter decision behind a stable port ("a service is just an adapter that moved into its own process; the domain does not change"). In `execution-topology.md`, record one row per port: its adapter, its location family (the five in `shared-bricks.md`), the class(es) of data crossing it, its sovereignty level, and the trigger to move it. Any placement that is not in-app is an ADR. Sovereignty is graduated by data class, not set wholesale — and traces are data: a third-party trace export is a decision, not a default. Seed the usage registry here too: risk is classed by deployment, not by platform. runward traces this decision; it never deploys.
34
+
31
35
  **Lock structuring choices in ADRs.** Starting topology, core language, legacy integration strategy, bounded-context boundaries: each goes through `decision-loop` — reality-check against reference implementations, sourced state of the art, challenge, durable position, written lock. A decision that is not locked does not enter the architecture note.
32
36
 
33
37
  **Confront the architect craft rules at the point of deciding.** Open the CRITICAL/HIGH rules mapped to the architect phase (`runward/rules/`, `phases: [architect]`): contract governance, the hexagonal architecture and adapter pattern, the single core language, the ADR-and-journal discipline. Do not work from their names — read them. Account for each in the `Rule conformance` manifest of the architecture note: `applied` with a pointer, `deviated` with an ADR, or `n/a` with a reason. `runward check --strict` verifies that manifest.
@@ -38,6 +42,7 @@ Use this workflow once framing is decided and structure must follow: "how do we
38
42
 
39
43
  - Architecture note produced, boundaries first, stack open.
40
44
  - Every port named with contract and initial version; integration protocol stated.
45
+ - Execution-topology note produced: every port placed behind a location family, with its data class(es) and sovereignty; non-in-app placements carry an ADR; the usage registry is seeded.
41
46
  - One ADR per structuring choice, locked before the note mentions it.
42
47
  - Every architect CRITICAL/HIGH rule accounted for in the conformance manifest (`runward check --strict`).
43
48
  - Note reviewed via `review` before circulation.
@@ -34,12 +34,14 @@ Use this workflow once a floor is running and an evolution is on the table: "do
34
34
  1. **Lock the decision in an ADR before implementing.** Run `decision-loop`: reality-check, sourced state of the art, challenge, durable position, written lock. No lock, no code.
35
35
  2. **Keep the evolution behind a boundary.** An extraction goes through the port already in place; a new agent through the registry; a tier change through the model gateway. The domain does not change; only the adapter and the topology do. Nothing here is irreversible, because everything is decided behind a boundary.
36
36
  3. **Prove the gain after the switch.** Load holds, latency drops, quality rises, cost stays controlled. If the gain is not there, roll back — the boundary makes rollback possible.
37
+ 4. **Reopen the execution-topology note when a switch moves a placement.** Any evolution that changes where a port's adapter runs (extraction into a service, state externalization, a brick moving to shared infrastructure) updates that port's row in `execution-topology.md`: new location family, data class, sovereignty, and the ADR that locked it. The placement decision stays traced, gate to gate — the second vision does not drift behind the first.
37
38
 
38
39
  ## Definition of Done
39
40
 
40
41
  - Every shipped evolution maps to an observed trigger or a measured gain.
41
42
  - One locked ADR per switch, including the rollback signal.
42
43
  - The domain untouched; only adapters and topology moved.
44
+ - `execution-topology.md` reopened and updated for any switch that moved a port's placement.
43
45
  - Post-switch measurement recorded; rollbacks executed where the gain failed.
44
46
 
45
47
  ## Anti-patterns
@@ -26,7 +26,7 @@ Use this workflow whenever an agentic-system mission needs to be planned, advanc
26
26
  **Run the six phases, each behind a gate.**
27
27
 
28
28
  1. **Frame.** Light immersion in the real process. Output: the floor/target split and an observable success criterion. Delegate to `frame`.
29
- 2. **Architect.** Boundaries before stack: domain ports, model port, integration protocol. Language and topology stay open. Delegate to `architect`.
29
+ 2. **Architect.** Boundaries before stack: domain ports, model port, integration protocol. Language and topology stay open. Two visions live behind the same ports — the application domain (what the system does) and the execution topology (where, and under which sovereignty, each port's adapter runs); runward traces and governs both, deploys neither. Delegate to `architect`.
30
30
  3. **Floor.** The smallest system that proves value on real traffic: one orchestrator, a model port, persistence, guardrails, baseline observability. Delegate to `floor`.
31
31
  4. **Iterate.** Add complexity only on an objective trigger or a measured gain, one ADR per switch. Delegate to `iterate`.
32
32
  5. **Govern.** Transversal, wired from day zero: single middleware chain, cost ceilings, human approval on sensitive actions, continuous evaluation. Delegate to `govern`.