@opetope/runtime 0.1.0
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.
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +345 -0
- package/README.ru.md +344 -0
- package/dist/application-compiler-edges.d.ts +3 -0
- package/dist/application-compiler-edges.js +2 -0
- package/dist/application-compiler-edges.js.map +1 -0
- package/dist/application-compiler-graph.d.ts +8 -0
- package/dist/application-compiler-graph.js +2 -0
- package/dist/application-compiler-graph.js.map +1 -0
- package/dist/application-compiler.d.ts +116 -0
- package/dist/application-compiler.js +2 -0
- package/dist/application-compiler.js.map +1 -0
- package/dist/application-conditions.d.ts +18 -0
- package/dist/application-conditions.js +2 -0
- package/dist/application-conditions.js.map +1 -0
- package/dist/application-definition.d.ts +30 -0
- package/dist/application-definition.js +2 -0
- package/dist/application-definition.js.map +1 -0
- package/dist/application-error.d.ts +10 -0
- package/dist/application-error.js +2 -0
- package/dist/application-error.js.map +1 -0
- package/dist/application-execution.d.ts +43 -0
- package/dist/application-execution.js +2 -0
- package/dist/application-execution.js.map +1 -0
- package/dist/application-feature-bindings.d.ts +11 -0
- package/dist/application-feature-bindings.js +2 -0
- package/dist/application-feature-bindings.js.map +1 -0
- package/dist/application-feature-instance.d.ts +5 -0
- package/dist/application-feature-instance.js +2 -0
- package/dist/application-feature-instance.js.map +1 -0
- package/dist/application-group-order.d.ts +29 -0
- package/dist/application-group-order.js +2 -0
- package/dist/application-group-order.js.map +1 -0
- package/dist/application-instance-retirement.d.ts +36 -0
- package/dist/application-instance-retirement.js +2 -0
- package/dist/application-instance-retirement.js.map +1 -0
- package/dist/application-open-options.d.ts +29 -0
- package/dist/application-open-options.js +2 -0
- package/dist/application-open-options.js.map +1 -0
- package/dist/application-port-compiler.d.ts +28 -0
- package/dist/application-port-compiler.js +2 -0
- package/dist/application-port-compiler.js.map +1 -0
- package/dist/attachment-call-declaration.d.ts +43 -0
- package/dist/attachment-call-declaration.js +2 -0
- package/dist/attachment-call-declaration.js.map +1 -0
- package/dist/attachment-declaration.d.ts +68 -0
- package/dist/attachment-declaration.js +2 -0
- package/dist/attachment-declaration.js.map +1 -0
- package/dist/attachment-execution.d.ts +9 -0
- package/dist/attachment-execution.js +2 -0
- package/dist/attachment-execution.js.map +1 -0
- package/dist/attachment-retirement-scheduler.d.ts +14 -0
- package/dist/attachment-retirement-scheduler.js +2 -0
- package/dist/attachment-retirement-scheduler.js.map +1 -0
- package/dist/call-option-snapshot.d.ts +20 -0
- package/dist/call-option-snapshot.js +2 -0
- package/dist/call-option-snapshot.js.map +1 -0
- package/dist/compile-call-target-bindings.d.ts +14 -0
- package/dist/compile-call-target-bindings.js +2 -0
- package/dist/compile-call-target-bindings.js.map +1 -0
- package/dist/compile-module-template.d.ts +38 -0
- package/dist/compile-module-template.js +2 -0
- package/dist/compile-module-template.js.map +1 -0
- package/dist/condition-group-execution.d.ts +44 -0
- package/dist/condition-group-execution.js +2 -0
- package/dist/condition-group-execution.js.map +1 -0
- package/dist/condition-override.d.ts +28 -0
- package/dist/condition-override.js +2 -0
- package/dist/condition-override.js.map +1 -0
- package/dist/condition-source.d.ts +10 -0
- package/dist/condition-source.js +2 -0
- package/dist/condition-source.js.map +1 -0
- package/dist/condition-types.d.ts +14 -0
- package/dist/condition.d.ts +28 -0
- package/dist/condition.js +2 -0
- package/dist/condition.js.map +1 -0
- package/dist/control-registry.d.ts +56 -0
- package/dist/control-registry.js +2 -0
- package/dist/control-registry.js.map +1 -0
- package/dist/dynamic-scope-child.d.ts +23 -0
- package/dist/dynamic-scope-child.js +2 -0
- package/dist/dynamic-scope-child.js.map +1 -0
- package/dist/dynamic-scope-controller.d.ts +16 -0
- package/dist/dynamic-scope-controller.js +2 -0
- package/dist/dynamic-scope-controller.js.map +1 -0
- package/dist/feature-attachment-authoring-types.d.ts +51 -0
- package/dist/feature-attachment-lowering.d.ts +18 -0
- package/dist/feature-attachment-lowering.js +2 -0
- package/dist/feature-attachment-lowering.js.map +1 -0
- package/dist/feature-attachment.d.ts +45 -0
- package/dist/feature-attachment.js +2 -0
- package/dist/feature-attachment.js.map +1 -0
- package/dist/feature-authoring-types.d.ts +196 -0
- package/dist/feature-authoring.d.ts +15 -0
- package/dist/feature-authoring.js +2 -0
- package/dist/feature-authoring.js.map +1 -0
- package/dist/feature-body.d.ts +53 -0
- package/dist/feature-body.js +2 -0
- package/dist/feature-body.js.map +1 -0
- package/dist/feature-call-authority.d.ts +8 -0
- package/dist/feature-call-authority.js +2 -0
- package/dist/feature-call-authority.js.map +1 -0
- package/dist/feature-call-types.d.ts +39 -0
- package/dist/feature-call.d.ts +18 -0
- package/dist/feature-call.js +2 -0
- package/dist/feature-call.js.map +1 -0
- package/dist/feature-calls.d.ts +6 -0
- package/dist/feature-calls.js +2 -0
- package/dist/feature-calls.js.map +1 -0
- package/dist/feature-contract.d.ts +70 -0
- package/dist/feature-contract.js +2 -0
- package/dist/feature-contract.js.map +1 -0
- package/dist/feature-contribution-model.d.ts +38 -0
- package/dist/feature-contribution-model.js +2 -0
- package/dist/feature-contribution-model.js.map +1 -0
- package/dist/feature-contribution.d.ts +126 -0
- package/dist/feature-contribution.js +2 -0
- package/dist/feature-contribution.js.map +1 -0
- package/dist/feature-definition-api.d.ts +53 -0
- package/dist/feature-definition-support.d.ts +21 -0
- package/dist/feature-definition-support.js +2 -0
- package/dist/feature-definition-support.js.map +1 -0
- package/dist/feature-effect.d.ts +26 -0
- package/dist/feature-effect.js +2 -0
- package/dist/feature-effect.js.map +1 -0
- package/dist/feature-event.d.ts +32 -0
- package/dist/feature-event.js +2 -0
- package/dist/feature-event.js.map +1 -0
- package/dist/feature-generation.d.ts +34 -0
- package/dist/feature-generation.js +2 -0
- package/dist/feature-generation.js.map +1 -0
- package/dist/feature-lazy-generation.d.ts +6 -0
- package/dist/feature-lazy-generation.js +2 -0
- package/dist/feature-lazy-generation.js.map +1 -0
- package/dist/feature-lazy.d.ts +44 -0
- package/dist/feature-lazy.js +2 -0
- package/dist/feature-lazy.js.map +1 -0
- package/dist/feature-materialization-binding.d.ts +31 -0
- package/dist/feature-materialization-binding.js +2 -0
- package/dist/feature-materialization-binding.js.map +1 -0
- package/dist/feature-materialization-types.d.ts +45 -0
- package/dist/feature-model-dependencies.d.ts +23 -0
- package/dist/feature-model-dependencies.js +2 -0
- package/dist/feature-model-dependencies.js.map +1 -0
- package/dist/feature-model.d.ts +60 -0
- package/dist/feature-model.js +2 -0
- package/dist/feature-model.js.map +1 -0
- package/dist/feature-optional.d.ts +15 -0
- package/dist/feature-optional.js +2 -0
- package/dist/feature-optional.js.map +1 -0
- package/dist/feature-own-lowering.d.ts +22 -0
- package/dist/feature-own-lowering.js +2 -0
- package/dist/feature-own-lowering.js.map +1 -0
- package/dist/feature-port-binding.d.ts +6 -0
- package/dist/feature-port-binding.js +2 -0
- package/dist/feature-port-binding.js.map +1 -0
- package/dist/feature-port.d.ts +66 -0
- package/dist/feature-port.js +2 -0
- package/dist/feature-port.js.map +1 -0
- package/dist/feature-record.d.ts +6 -0
- package/dist/feature-record.js +2 -0
- package/dist/feature-record.js.map +1 -0
- package/dist/feature-resource.d.ts +29 -0
- package/dist/feature-resource.js +2 -0
- package/dist/feature-resource.js.map +1 -0
- package/dist/feature-scope-types.d.ts +35 -0
- package/dist/feature-scope.d.ts +29 -0
- package/dist/feature-scope.js +2 -0
- package/dist/feature-scope.js.map +1 -0
- package/dist/feature-stream.d.ts +37 -0
- package/dist/feature-stream.js +2 -0
- package/dist/feature-stream.js.map +1 -0
- package/dist/feature-timers.d.ts +14 -0
- package/dist/feature-timers.js +2 -0
- package/dist/feature-timers.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/inspection-activity-protocol.d.ts +63 -0
- package/dist/inspection-activity.d.ts +54 -0
- package/dist/inspection-activity.js +2 -0
- package/dist/inspection-activity.js.map +1 -0
- package/dist/inspection-diff.d.ts +10 -0
- package/dist/inspection-diff.js +2 -0
- package/dist/inspection-diff.js.map +1 -0
- package/dist/inspection-module-activity.d.ts +4 -0
- package/dist/inspection-module-activity.js +2 -0
- package/dist/inspection-module-activity.js.map +1 -0
- package/dist/inspection-observer.d.ts +38 -0
- package/dist/inspection-observer.js +2 -0
- package/dist/inspection-observer.js.map +1 -0
- package/dist/inspection-plan.d.ts +33 -0
- package/dist/inspection-plan.js +2 -0
- package/dist/inspection-plan.js.map +1 -0
- package/dist/inspection-protocol.d.ts +333 -0
- package/dist/inspection-protocol.js +2 -0
- package/dist/inspection-protocol.js.map +1 -0
- package/dist/inspection-registry.d.ts +41 -0
- package/dist/inspection-registry.js +2 -0
- package/dist/inspection-registry.js.map +1 -0
- package/dist/inspection-session.d.ts +92 -0
- package/dist/inspection-session.js +2 -0
- package/dist/inspection-session.js.map +1 -0
- package/dist/inspection-snapshot.d.ts +4 -0
- package/dist/inspection-snapshot.js +2 -0
- package/dist/inspection-snapshot.js.map +1 -0
- package/dist/inspection-state.d.ts +91 -0
- package/dist/inspection-state.js +2 -0
- package/dist/inspection-state.js.map +1 -0
- package/dist/instance-demand.d.ts +31 -0
- package/dist/instance-demand.js +2 -0
- package/dist/instance-demand.js.map +1 -0
- package/dist/internal.d.ts +41 -0
- package/dist/internal.js +2 -0
- package/dist/internal.js.map +1 -0
- package/dist/keyed-scope-controller.d.ts +11 -0
- package/dist/keyed-scope-controller.js +2 -0
- package/dist/keyed-scope-controller.js.map +1 -0
- package/dist/model-kernel.d.ts +18 -0
- package/dist/model-kernel.js +2 -0
- package/dist/model-kernel.js.map +1 -0
- package/dist/module-call-context.d.ts +7 -0
- package/dist/module-call-context.js +2 -0
- package/dist/module-call-context.js.map +1 -0
- package/dist/module-call-runtime.d.ts +21 -0
- package/dist/module-call-runtime.js +2 -0
- package/dist/module-call-runtime.js.map +1 -0
- package/dist/module-generation.d.ts +57 -0
- package/dist/module-generation.js +2 -0
- package/dist/module-generation.js.map +1 -0
- package/dist/module-instance-types.d.ts +156 -0
- package/dist/module-instance.d.ts +19 -0
- package/dist/module-instance.js +2 -0
- package/dist/module-instance.js.map +1 -0
- package/dist/module-runtime-identity.d.ts +4 -0
- package/dist/module-runtime-identity.js +2 -0
- package/dist/module-runtime-identity.js.map +1 -0
- package/dist/module-scope-open.d.ts +4 -0
- package/dist/module-scope-open.js +2 -0
- package/dist/module-scope-open.js.map +1 -0
- package/dist/module-scope-retirement.d.ts +3 -0
- package/dist/module-scope-retirement.js +2 -0
- package/dist/module-scope-retirement.js.map +1 -0
- package/dist/module-template-ir.d.ts +59 -0
- package/dist/owner-generation-retirement.d.ts +6 -0
- package/dist/owner-generation-retirement.js +2 -0
- package/dist/owner-generation-retirement.js.map +1 -0
- package/dist/owner-generation-state.d.ts +123 -0
- package/dist/owner-generation-state.js +2 -0
- package/dist/owner-generation-state.js.map +1 -0
- package/dist/owner-generation.d.ts +16 -0
- package/dist/owner-generation.js +2 -0
- package/dist/owner-generation.js.map +1 -0
- package/dist/public-module-definition.d.ts +9 -0
- package/dist/public-module-definition.js +2 -0
- package/dist/public-module-definition.js.map +1 -0
- package/dist/public-module-instance.d.ts +6 -0
- package/dist/public-module-instance.js +2 -0
- package/dist/public-module-instance.js.map +1 -0
- package/dist/public-module-retirement-diagnostics.d.ts +5 -0
- package/dist/public-module-retirement-diagnostics.js +2 -0
- package/dist/public-module-retirement-diagnostics.js.map +1 -0
- package/dist/public-module-retirement.d.ts +3 -0
- package/dist/public-module-retirement.js +2 -0
- package/dist/public-module-retirement.js.map +1 -0
- package/dist/public-module-scope.d.ts +5 -0
- package/dist/public-module-scope.js +2 -0
- package/dist/public-module-scope.js.map +1 -0
- package/dist/public-module-state.d.ts +28 -0
- package/dist/public-module-state.js +2 -0
- package/dist/public-module-state.js.map +1 -0
- package/dist/public-module-types.d.ts +295 -0
- package/dist/public-module.d.ts +4 -0
- package/dist/resource-cache.d.ts +12 -0
- package/dist/resource-cache.js +2 -0
- package/dist/resource-cache.js.map +1 -0
- package/dist/resource-controller.d.ts +29 -0
- package/dist/resource-controller.js +2 -0
- package/dist/resource-controller.js.map +1 -0
- package/dist/resource-policy.d.ts +16 -0
- package/dist/resource-policy.js +2 -0
- package/dist/resource-policy.js.map +1 -0
- package/dist/resource-snapshot.d.ts +12 -0
- package/dist/resource-snapshot.js +2 -0
- package/dist/resource-snapshot.js.map +1 -0
- package/dist/resource-types.d.ts +3 -0
- package/dist/runtime-error-reporting.d.ts +4 -0
- package/dist/runtime-error-reporting.js +2 -0
- package/dist/runtime-error-reporting.js.map +1 -0
- package/dist/stream-backpressure.d.ts +18 -0
- package/dist/stream-backpressure.js +2 -0
- package/dist/stream-backpressure.js.map +1 -0
- package/dist/stream-cleanup.d.ts +17 -0
- package/dist/stream-cleanup.js +2 -0
- package/dist/stream-cleanup.js.map +1 -0
- package/dist/stream-controller-types.d.ts +51 -0
- package/dist/stream-controller.d.ts +5 -0
- package/dist/stream-controller.js +2 -0
- package/dist/stream-controller.js.map +1 -0
- package/docs/agent-guide.md +214 -0
- package/docs/agent-guide.ru.md +208 -0
- package/docs/cookbook.md +734 -0
- package/docs/cookbook.ru.md +729 -0
- package/docs/decisions.md +1437 -0
- package/docs/devtools.md +423 -0
- package/docs/devtools.ru.md +419 -0
- package/docs/how-it-works.md +521 -0
- package/docs/how-it-works.ru.md +495 -0
- package/docs/releases.md +78 -0
- package/docs/releases.ru.md +78 -0
- package/docs/spec.md +874 -0
- package/docs/spec.ru.md +884 -0
- package/package.json +72 -0
package/docs/devtools.md
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
# Opetope Devtools
|
|
2
|
+
|
|
3
|
+
One truthful projection of the compiled topology and the current runtime state, and an answer to the question
|
|
4
|
+
"why is this node in this state". `@opetope/devtools` is an optional development tool; keep its integration out of production bundles.
|
|
5
|
+
|
|
6
|
+
This document is normative for the devtools: the observation protocol, the architecture that delivers it, the panel
|
|
7
|
+
that renders it, and the condition control that writes back. Where this document and
|
|
8
|
+
the runtime disagree, the runtime and [`spec.md`](spec.md) win, and the difference is recorded in §9.
|
|
9
|
+
|
|
10
|
+
## 1. What it is and what it is not
|
|
11
|
+
|
|
12
|
+
It answers: why a feature is not active; what it is waiting for; which instance is current and which retired; what
|
|
13
|
+
its contributions reach; what happened before a failure.
|
|
14
|
+
|
|
15
|
+
It does not read model or repository values, React props or call payloads. It does not replay side effects, edit the
|
|
16
|
+
declaration graph, replace feature flags, or reach the network. Error text never enters the protocol: a failure is a
|
|
17
|
+
bounded code, a subject and a phase.
|
|
18
|
+
|
|
19
|
+
## 2. Observation protocol
|
|
20
|
+
|
|
21
|
+
### 2.1 Two projections
|
|
22
|
+
|
|
23
|
+
A snapshot carries `plan` — the immutable topology the application compiler produced — and `runtime` — the live
|
|
24
|
+
world it opened. They are never merged: a feature declaration outlives many instances of it, and collapsing the two
|
|
25
|
+
is the mistake this protocol exists to prevent. Topology is immutable inside one session; a plan replacement opens a
|
|
26
|
+
new `sessionId`.
|
|
27
|
+
|
|
28
|
+
### 2.2 Nodes
|
|
29
|
+
|
|
30
|
+
Named with the public words of [`spec.md`](spec.md) §3: `application`, `condition`, `condition-group`, `feature`,
|
|
31
|
+
`feature-instance`, `attachment`, `contribution`, `contribution-target`, `port`, `host-contract`, `absent-provider`.
|
|
32
|
+
|
|
33
|
+
A condition group is the inferred owner of activation: features whose `when` names the same set of conditions form
|
|
34
|
+
one group. A feature with an empty `when` is owned by the application itself. `absent-provider` is a feature export
|
|
35
|
+
contract the plan names with nothing behind it, so an `absent-import` edge has an origin to point at.
|
|
36
|
+
|
|
37
|
+
### 2.3 Edges
|
|
38
|
+
|
|
39
|
+
Orientation is fixed: provider, blocker or owner → consumer, waiter or owned node. Three classes:
|
|
40
|
+
|
|
41
|
+
- **hard** — `hard-import`, `requires-port`. The only edges an activation waits on; they carry `pending`.
|
|
42
|
+
- **weak** — `optional-import`, `optional-port`, `absent-import`, `absent-port`. One word, `optional`, for features
|
|
43
|
+
and ports. The consumer may outlive the provider, reads a `Lookup` (`found` or `missing`), and never waits.
|
|
44
|
+
- **structural** — `owns`, `provides-port`, `contributes-to`, `condition-control`, `on-demand-import`. No state of
|
|
45
|
+
their own.
|
|
46
|
+
|
|
47
|
+
The compiler emits one `requires-port` kind for both strengths; the protocol splits it by
|
|
48
|
+
`CompiledApplicationRequirementIR.optional`, symmetrically to the import pair the compiler splits itself.
|
|
49
|
+
|
|
50
|
+
### 2.4 State automata and the three axes
|
|
51
|
+
|
|
52
|
+
There is no universal `node.status`. Each kind carries the automaton the runtime actually has: the condition group
|
|
53
|
+
from `condition-group-execution.ts`, the feature instance from the `FeatureError` codes of `spec.md` §4, the
|
|
54
|
+
attachment from `AttachmentLifecycleSnapshot`, the contribution from the hub (`published`, and `withheld` when its
|
|
55
|
+
`when` reads false), the port from the compiler's single-provider selection, the host contract from `bind`.
|
|
56
|
+
|
|
57
|
+
The panel styles nodes from three orthogonal axes derived from those unions, never the reverse:
|
|
58
|
+
|
|
59
|
+
- `desired` — `active` or `inactive`, with source, override and effective values;
|
|
60
|
+
- `lifecycle` — `declared`, `waiting`, `opening`, `ready`, `fencing`, `retiring`, `closed`;
|
|
61
|
+
- `health` — `healthy`, `blocked`, `failed`, `cancelled`, `stale`, `quarantined`.
|
|
62
|
+
|
|
63
|
+
`declared` is the seventh lifecycle value: a feature the plan declares while nothing has opened it is in none of the
|
|
64
|
+
other six, and calling it `waiting` would claim a blocker that does not exist.
|
|
65
|
+
|
|
66
|
+
Fetching a header's `body` adds no eighth value. The instance exists and is `opening` until its code arrives, which
|
|
67
|
+
is what the runtime reports and what the panel draws (D186); a `loading` of its own would have been a second word for
|
|
68
|
+
a state the protocol already has, and the legend would have had to explain the difference.
|
|
69
|
+
|
|
70
|
+
### 2.5 Failures, phases and causes
|
|
71
|
+
|
|
72
|
+
A failure is the runtime's own error, as a discriminated pair of `subject` and `code`: `feature`, `call`, `readable`,
|
|
73
|
+
`contribution`, `application`, `declaration`. Phases are the vocabulary of D14: `open`, `close`, `rollback`, `fence`,
|
|
74
|
+
`drain`, `retry`. A cause is a ref the panel resolves against the snapshot, never prose.
|
|
75
|
+
|
|
76
|
+
The protocol has exactly one clock, `cause.at`, and it stamps the trigger rather than the commit. States carry no
|
|
77
|
+
timestamps: a second clock would let the panel show durations the runtime never measured. A frame with no cause has
|
|
78
|
+
no time, and the timeline renders that column empty.
|
|
79
|
+
|
|
80
|
+
When present, `cause.at` is a non-negative integer of epoch milliseconds no greater than
|
|
81
|
+
`8_640_000_000_000_000`, the maximum valid Date timestamp. Sequence and revision counters retain their separate
|
|
82
|
+
non-negative safe-integer domain (D242).
|
|
83
|
+
|
|
84
|
+
### 2.6 Identity, labels and redaction
|
|
85
|
+
|
|
86
|
+
Ids are data, and one id names one thing. A diagnostic id is a kind prefix and the declaration ids of its parts,
|
|
87
|
+
joined with `:`; a part is escaped with `_` first, so a character the consumer's grammar rejects — `/` in a
|
|
88
|
+
declaration id, `|` in the key of a condition group — survives as itself rather than being folded onto its
|
|
89
|
+
neighbour, and the parts of a composed id stay separable (D200). A label is presentation metadata the compiler
|
|
90
|
+
derived from a declaration — the panel renders it and never translates it, and it is never authority. Snapshots
|
|
91
|
+
hold no callbacks, no `FeatureInstance`, no models or resources, no raw `Error`, `AbortSignal` or thenable, no DOM or React instance. Export is canonical data-only JSON
|
|
92
|
+
under record limits, delivered to the clipboard or a local file; there is no network transport.
|
|
93
|
+
|
|
94
|
+
Application nodes use `application:<encoded declaration id>`; feature nodes keep their encoded declaration id.
|
|
95
|
+
An application and a feature may share a declaration id while their diagnostic identities remain distinct (D242).
|
|
96
|
+
|
|
97
|
+
## 3. Observation architecture
|
|
98
|
+
|
|
99
|
+
### 3.1 Ownership of semantics
|
|
100
|
+
|
|
101
|
+
The runtime owns ids, kinds, transitions and cause refs. Devtools does not scan private maps, patch methods or
|
|
102
|
+
reconstruct the graph from logs, so the production scheduler and the debugger describe one machine.
|
|
103
|
+
`RuntimeInspectionSession` is a _consumed_ interface: the runtime owns its semantics and imports nothing from here,
|
|
104
|
+
so nothing in the runtime knows this package exists. The protocol, the reducer, the projections and the panel import
|
|
105
|
+
no `@opetope/*` package either. The one exception is `./integration`, which opens the ports on behalf of the
|
|
106
|
+
application and therefore holds `@opetope/runtime/internal` (D248).
|
|
107
|
+
|
|
108
|
+
### 3.2 Attaching
|
|
109
|
+
|
|
110
|
+
`createInspectionSession(execution)` on `@opetope/runtime/internal`, reachable only by holding the
|
|
111
|
+
`ApplicationExecution` the host created. No process-wide singleton, no global hook. Without a session the runtime
|
|
112
|
+
allocates no records, and closing the application takes the session down with it.
|
|
113
|
+
|
|
114
|
+
An application never calls that factory. It hands the execution to `installDevtools({ execution, … })` from
|
|
115
|
+
`@opetope/devtools/integration`, which opens the observation port — and, under `mode: 'control'`, the write port —
|
|
116
|
+
and owns both until its handle closes: `/internal` is a seam between the library's own packages, not a surface an
|
|
117
|
+
application is meant to reach (D248).
|
|
118
|
+
|
|
119
|
+
The attach returns a detach, and it belongs to whoever owns the execution's lifetime, not to whoever opened it. The
|
|
120
|
+
panel arrives through a dynamic import, so an application closed before that import lands would otherwise get a
|
|
121
|
+
panel it no longer has, and the next application would find the previous one still on the page.
|
|
122
|
+
|
|
123
|
+
### 3.3 Delivery, gaps and resync
|
|
124
|
+
|
|
125
|
+
The runtime commits a frame and notifies; the client reads the batch itself, so a slow or closed panel creates no
|
|
126
|
+
backpressure. Delivery may be out of band — the listener is not called inside the transition. A frame names its
|
|
127
|
+
session, its base and next revision and its sequence range; a gap, a duplicate, a revision jump or a foreign session
|
|
128
|
+
is rejected in favour of a fresh snapshot, because a partially applied delta is a debugger that lies. History is
|
|
129
|
+
bounded by both frame count and approximate bytes, and overflow leaves a gap marker rather than a silent hole.
|
|
130
|
+
|
|
131
|
+
The producer's `createInspectionSession(execution, { ringCapacity })` accepts a non-negative safe integer,
|
|
132
|
+
defaulting to 256 frames. Zero retains no frames: snapshots remain available, and a reader behind a dropped
|
|
133
|
+
transition receives `resync-required`. Invalid capacities throw before taking the observation slot (D242).
|
|
134
|
+
A frame with empty `operations` still commits a transaction when it carries an `activity` replacement, including
|
|
135
|
+
an empty replacement that clears the previous activity. A frame with neither change is rejected by the contract kit.
|
|
136
|
+
|
|
137
|
+
### 3.4 Demand neutrality
|
|
138
|
+
|
|
139
|
+
Zero, one or many attached inspectors leave the application's own outcomes identical. Observation creates no demand,
|
|
140
|
+
holds no instance alive and never changes what a feature publishes.
|
|
141
|
+
|
|
142
|
+
## 4. Panel
|
|
143
|
+
|
|
144
|
+
### 4.1 Two tabs
|
|
145
|
+
|
|
146
|
+
`Graph` and `Timeline`. One shows the graph at a moment, the other the history of commits. Everything that was once
|
|
147
|
+
a tab is now a filter inside `Graph`.
|
|
148
|
+
|
|
149
|
+
### 4.2 Edge filter chips and presets
|
|
150
|
+
|
|
151
|
+
A chip turns one edge kind on or off; a preset is a named set of chips.
|
|
152
|
+
|
|
153
|
+
| Preset | Turns on | Answers |
|
|
154
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
155
|
+
| Imports and ports | `hard-import`, `optional-import`, `absent-import`, `on-demand-import`, `requires-port`, `optional-port`, `absent-port`, `provides-port` | Who needs whom, and what of it is unavailable now |
|
|
156
|
+
| Conditions | `condition-control`, `owns` from the condition group | What gates this feature, and what else lives in its group |
|
|
157
|
+
| Ownership | `owns` | What this node created, and what outlives it |
|
|
158
|
+
| Extension points | `contributes-to`, `owns` down to contributions, plus a second projection step | Where this feature's contributions go, and who else is there |
|
|
159
|
+
|
|
160
|
+
The first three are honest edge filters. The fourth is the exception, and it is named as one: a contribution is a
|
|
161
|
+
node _between_ a feature and a target, so a one-hop neighbourhood shows the contributions but not where they go —
|
|
162
|
+
the very question it is opened for. It projects two steps, feature → contributions → targets, and shows the other
|
|
163
|
+
contributors to the same target, because priority and competition are only visible side by side. Selecting a target
|
|
164
|
+
inverts the projection.
|
|
165
|
+
|
|
166
|
+
### 4.3 Selection, breadcrumbs, inspector
|
|
167
|
+
|
|
168
|
+
One selection, one graph, one inspector. Changing a preset never changes the selection, and selecting never resets
|
|
169
|
+
the preset. Breadcrumbs show the path to the selected node and navigate: an ancestor crumb returns to it, the
|
|
170
|
+
current node stays a label. The inspector shows identity and provenance, the current state and instance counter, the
|
|
171
|
+
immediate cause, what the node depends on with each weak row's `Lookup`, what it owns, and the last causal chain.
|
|
172
|
+
|
|
173
|
+
A name too long for its box is shortened by the panel, not by the box: a neighbour is written relative to the
|
|
174
|
+
selection, a name that still overflows is cut from the middle so its last segment survives, a gated group says
|
|
175
|
+
`when` once and names its conditions in the tag beside it, and the full id is always in `title`. An ellipsis on the
|
|
176
|
+
right would eat the tail, and the tail is what tells two siblings apart.
|
|
177
|
+
|
|
178
|
+
### 4.4 Narrow screens
|
|
179
|
+
|
|
180
|
+
Below 1100 px the three columns stop being readable and the neighbourhood is drawn as a list. The list and the
|
|
181
|
+
inspector then share the second column, stacked, rather than one of them being dropped: a panel that cannot say what
|
|
182
|
+
a node is waiting for has lost the question it exists to answer. There is no manual `Graph`/`List` switch at any
|
|
183
|
+
width: the list adds nothing the graph does not give, and a second form costs a second layout, second tests and
|
|
184
|
+
second dictionary strings.
|
|
185
|
+
|
|
186
|
+
### 4.5 Status bar
|
|
187
|
+
|
|
188
|
+
Always visible, answering "can I trust what I am looking at": `frame · state rev`, the `dropped` count, and `LIVE`.
|
|
189
|
+
Behind `⋯`: `reducer · paint ms`, the theme source and the schema revision — those describe the panel, not the
|
|
190
|
+
application.
|
|
191
|
+
|
|
192
|
+
### 4.6 State coding, legend, locale
|
|
193
|
+
|
|
194
|
+
State is never carried by colour alone: glyph, colour and a text tag together, each with a screen-reader label. One
|
|
195
|
+
collapsible legend covers the seven lifecycle states, the three visual edge kinds with the protocol edges each one
|
|
196
|
+
covers, and the three axis badges.
|
|
197
|
+
|
|
198
|
+
The panel speaks English and Russian, defaulting to the browser and switchable in the header. The prose is
|
|
199
|
+
translated; the vocabulary of the protocol is not — `Feature`, `Condition`, `declared`, `quarantined`, `hard-import`,
|
|
200
|
+
`found`, `missing`, `drain` are identifiers a developer greps for and matches against `spec.md` §3, so they are
|
|
201
|
+
placed _into_ translated sentences rather than replaced. A test renders the panel in Russian across every surface and
|
|
202
|
+
fails on any Latin word outside two lists: the protocol vocabulary, and words the graph itself supplied.
|
|
203
|
+
|
|
204
|
+
### 4.7 Camera
|
|
205
|
+
|
|
206
|
+
The lens clips its content, so reaching a node outside the frame is the camera's job: drag empty canvas to pan, wheel
|
|
207
|
+
to zoom around the cursor, buttons for a deliberate 1.2× around the centre, fit-to-view when a node is opened. Wheel
|
|
208
|
+
zoom scales with the size of the gesture rather than by a fixed notch, `deltaMode` is normalized to pixels, and one
|
|
209
|
+
event is clamped so a coarse device cannot cross the range. A drag moves the camera and selects nothing.
|
|
210
|
+
|
|
211
|
+
## 5. Condition control
|
|
212
|
+
|
|
213
|
+
### 5.1 What may be controlled
|
|
214
|
+
|
|
215
|
+
Only a condition and its group. A graph node is a diagnostic record, not a mutable handle: writing state directly
|
|
216
|
+
would bypass the scheduler, hard dependencies, contributions, the fence, cleanup and quarantine. A feature with an
|
|
217
|
+
empty `when` cannot be switched off, and that is the law rather than a gap — synthetic conditions on every feature
|
|
218
|
+
were rejected, because they would make the debugger a source of lifetime the plan does not have.
|
|
219
|
+
|
|
220
|
+
### 5.2 Actions
|
|
221
|
+
|
|
222
|
+
| Action | Where it lives | What it does | What it does not promise |
|
|
223
|
+
| --------------- | ------------------------- | ------------------------------------------------------------------------ | ------------------------------------------- |
|
|
224
|
+
| `Suspend` | condition group node | Sets a forced-false override; the group closes by its normal transaction | It does not cancel the product's own source |
|
|
225
|
+
| `Resume` | condition group node | Drops the override; the group follows its source again | It does not promise `ready` |
|
|
226
|
+
| `Retry cleanup` | quarantined instance node | Retries the quarantined frontier through the existing retry contract | It does not declare a stuck resource closed |
|
|
227
|
+
|
|
228
|
+
Retry cleanup lives on the instance because quarantine belongs to the instance: its cleanup is the one that did not
|
|
229
|
+
settle. It is offered only where a quarantined frontier exists, which an application reaches by opening with
|
|
230
|
+
`cleanupFailure: 'quarantine'` (D182); under the default `report` there is no quarantine and therefore no button.
|
|
231
|
+
|
|
232
|
+
The commands sit in two places, and neither is the card itself: the bar over the canvas, which belongs to the
|
|
233
|
+
selected node without being drawn inside it, and the inspector. A card stays a single selection target — a control
|
|
234
|
+
nested in it would be a button inside a button, and a control floating over it would move with every pan.
|
|
235
|
+
|
|
236
|
+
An override badge sits beside the commands while an override stands, read from the group's own snapshot rather than
|
|
237
|
+
from what the panel asked for. Applying asks for confirmation, because it changes a live application, and the second
|
|
238
|
+
press names its target. Every application and every rejection reaches the timeline as an audit row.
|
|
239
|
+
|
|
240
|
+
### 5.3 Dependency law
|
|
241
|
+
|
|
242
|
+
There is no reverse-demand cascade. Suspending a provider's group sends hard dependents through their normal
|
|
243
|
+
unavailable and retirement semantics, while weak consumers keep running and read `missing`. Suspending a consumer's
|
|
244
|
+
group does not switch the provider off: losing a consumer disables nobody.
|
|
245
|
+
|
|
246
|
+
### 5.4 Write port contract
|
|
247
|
+
|
|
248
|
+
A separate factory beside the observation port, because holding an inspection session must not grant the right to
|
|
249
|
+
write.
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
interface RuntimeControlSession {
|
|
253
|
+
close(): void;
|
|
254
|
+
resume(conditionId: string): Promise<ControlOutcome>;
|
|
255
|
+
retryCleanup(instanceId: string): Promise<ControlOutcome>;
|
|
256
|
+
suspend(conditionId: string): Promise<ControlOutcome>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
type ControlOutcome =
|
|
260
|
+
| { readonly kind: 'applied'; readonly sequence: number }
|
|
261
|
+
| { readonly kind: 'rejected'; readonly reason: 'not-controllable' | 'stale' | 'unknown-target' };
|
|
262
|
+
|
|
263
|
+
declare function createControlSession(execution: ApplicationExecution): RuntimeControlSession;
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Application is serialized by the same scheduler that runs condition transitions and publishes an ordinary atomic
|
|
267
|
+
frame, so the panel learns the result the way it learns everything else; `sequence` points at the frame where the
|
|
268
|
+
effect is visible. Rejection is data, not an exception. One execution has at most one live control session: a second
|
|
269
|
+
`createControlSession` throws while the first is open, and `close()` frees the slot so a reloaded panel may take
|
|
270
|
+
control again (D191). The host does not open it either: `installDevtools` does, under `mode: 'control'`, and a
|
|
271
|
+
refusal leaves a failed install rather than a panel that silently came up read-only (D248). `close()` also drops
|
|
272
|
+
every override that session set, so closing the panel leaves the application undebugged, and an override never
|
|
273
|
+
survives a reload or HMR.
|
|
274
|
+
|
|
275
|
+
Control commands use the current `/3` graph/frame schema without a separate revision. An applied command is expressible: the frame carries
|
|
276
|
+
the existing cause `control-command` with its condition, and the group's snapshot fills the existing
|
|
277
|
+
`desired.override: 'force-inactive'` beside `desired.source`, which is what the override badge reads. A rejection
|
|
278
|
+
emits no frame at all, because a frame that commits nothing would be a lie.
|
|
279
|
+
|
|
280
|
+
The audit trail is therefore the panel's own: it writes local timeline rows — `control · suspend cond.x · applied →
|
|
281
|
+
frame N`, `control · resume cond.y · rejected · unknown-target` — and links an applied row to the producer's frame by
|
|
282
|
+
the `sequence` the outcome returned. The owner's requirement that every application and every rejection be visible in
|
|
283
|
+
one ribbon is met without inventing a frame the runtime never committed.
|
|
284
|
+
|
|
285
|
+
`preview → apply` with a one-shot permit is not adopted. There is one controllable subject, its effect is described
|
|
286
|
+
by the dependency law and already drawn in the graph, so a preview would duplicate the graph; confirmation plus an
|
|
287
|
+
audit trail is the proportionate form. If control ever gains a second kind of target, the question reopens.
|
|
288
|
+
|
|
289
|
+
## 6. Performance and memory
|
|
290
|
+
|
|
291
|
+
A snapshot builds in `O(N + E)` with no recursive walk on the ingest path; a frame costs `O(changed records)`. The
|
|
292
|
+
tree caches declarative topology for the session and resolves display per rendered row, so a state-only commit does
|
|
293
|
+
not rebuild it. Search, the inspector projection and the graph layout are memoized on the store's version counter,
|
|
294
|
+
which changes exactly once per applied frame. List/inspector virtualization from 200 rows and worker layout from
|
|
295
|
+
300 nodes are proposed thresholds, not implemented behaviour or measured guarantees. The current list and layout
|
|
296
|
+
remain on the main thread; a future implementation needs corpus measurements at those boundaries. The
|
|
297
|
+
panel holds itself to a p95 of 4 ms to apply a frame of at most 100 changed records and to one animation frame to
|
|
298
|
+
paint it, and its own status bar reports both, so a regression shows up where it happens rather than in a report.
|
|
299
|
+
|
|
300
|
+
A surface the session has not asked for is not in the panel's bundle at all. Four load on demand, each with a budget
|
|
301
|
+
of its own: the control plane when a host passes a write port and the selection has something it can address, the
|
|
302
|
+
legend the first time it is opened, the exporter on the click that uses it, and the timeline with the tab that shows
|
|
303
|
+
it. Each carries its own dictionary as well, so a panel that never opens one ships neither its code nor its words.
|
|
304
|
+
That is what keeps the panel budget honest as the panel grows: 20.18 kB of 21 today, against 1.68, 1.45, 1.14 and
|
|
305
|
+
0.91 kB behind their own caps, and 5.02 of 5.5 kB for the headless session consumer (D240, D241).
|
|
306
|
+
|
|
307
|
+
A surface can also fail to arrive. The panel holds each one as pending, loaded or failed: a failed surface says so
|
|
308
|
+
where it would have been and offers to ask again, an in-flight fetch is shared rather than started twice, and a
|
|
309
|
+
failed attempt is forgotten so the retry can really retry. Nothing is left as a rejected promise for the host to
|
|
310
|
+
discover — the same rule the copy and the export outcomes follow (§7).
|
|
311
|
+
|
|
312
|
+
Measure the host application separately with inspection disabled, attached and detached. Record build mode,
|
|
313
|
+
initial and demand-loaded bytes, interaction latency, and retained memory. Application-specific loading and
|
|
314
|
+
backend behaviour are not library performance guarantees.
|
|
315
|
+
|
|
316
|
+
## 7. Failure semantics
|
|
317
|
+
|
|
318
|
+
| Event | Required behaviour |
|
|
319
|
+
| -------------------------- | ----------------------------------------------------------- |
|
|
320
|
+
| Observer throws | Detached and reported; the transition is already committed |
|
|
321
|
+
| Queue overflow | Bounded gap marker, snapshot resync, application unblocked |
|
|
322
|
+
| Duplicate or out-of-order | Reject the frame and resync |
|
|
323
|
+
| Frame from a replaced plan | Ignore |
|
|
324
|
+
| Snapshot capture failure | Close the session, report `snapshot-failed`, change nothing |
|
|
325
|
+
| Cleanup quarantined | The logical fence stands; only an exact retry |
|
|
326
|
+
| Panel unmounted | Detach the consumer, free UI state, leave the application |
|
|
327
|
+
|
|
328
|
+
Untrusted payloads — a replayed export, a fixture from a bug report — pass a strict decoder with budgets on record
|
|
329
|
+
counts, string length and bytes. Unknown kinds and unexpected fields are rejected with the record rather than
|
|
330
|
+
tolerated; the schema revision, not leniency, carries compatibility forward. An accessor is rejected before it is
|
|
331
|
+
read — a field of a record and an entry of an array alike, and no method of the incoming value is called — so a
|
|
332
|
+
payload cannot execute code inside the decoder. The byte budget is counted in UTF-8 bytes before the parse, because
|
|
333
|
+
a limit named in bytes that counts UTF-16 units is three times looser than it reads.
|
|
334
|
+
|
|
335
|
+
Ids are held to their own lengths, derived from the producer's laws rather than chosen: an atom is a declaration id
|
|
336
|
+
of at most 160 characters, its escaping can add one character per separator (D200), a node id adds the longest
|
|
337
|
+
prefix, and an edge id escapes both endpoints again. A condition is published as one escaped atom and addressed by
|
|
338
|
+
it, with the declaration id the author wrote left in the label, so it is bounded by the escaped length and read by
|
|
339
|
+
the label everywhere the panel shows it. A flat cap shorter than that arithmetic refuses snapshots a legal
|
|
340
|
+
application produces. A group whose key joins more conditions than one escaped atom can hold is refused as an
|
|
341
|
+
oversized id, which is a budget refusal like `maxNodes` and not a claim about the shape. Contribution priorities
|
|
342
|
+
accept every finite number, including negative and fractional values, matching the runtime producer; counts and
|
|
343
|
+
indexes retain their own integer constraints (D212).
|
|
344
|
+
|
|
345
|
+
## 8. Acceptance
|
|
346
|
+
|
|
347
|
+
Topology and instances stay distinct. A snapshot plus contiguous frames deterministically reproduces the state at
|
|
348
|
+
every commit boundary. A frame is visible only after an atomic commit. Stale, duplicate, gapped and foreign-session
|
|
349
|
+
frames are rejected. Every waiting, failed or quarantined state carries a typed reason. Nothing reads as runnable
|
|
350
|
+
after its owner is fenced. The number of inspectors changes no outcome. Production contains no devtools chunk.
|
|
351
|
+
|
|
352
|
+
The executable form is `assertInspectionSessionContract` in `@opetope/devtools/testing`: a runtime implementing the
|
|
353
|
+
port runs it against its own session without reading devtools sources.
|
|
354
|
+
|
|
355
|
+
## 9. Decision log
|
|
356
|
+
|
|
357
|
+
Rows describing current behaviour. Superseded rows are collapsed into one line each rather than kept in full.
|
|
358
|
+
|
|
359
|
+
### 9.1 Protocol
|
|
360
|
+
|
|
361
|
+
| № | Decision |
|
|
362
|
+
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
363
|
+
| T1 | A frame has no clock of its own. `cause.at` is the only one and it stamps the trigger; state records carry no timestamps, and no duration is derived from commit times |
|
|
364
|
+
| T3 | `declared` is the seventh lifecycle value, for a feature the plan declares while nothing has opened it |
|
|
365
|
+
| T4 | Node taxonomy rebuilt from the plan IR and the public vocabulary; `activation-domain`, `module-*`, `eligibility-attempt`, `capability-resolution`, `scope-generation`, `view-root` are gone |
|
|
366
|
+
| T6 | Six activation semantics collapse to two classes the compiler distinguishes: hard edges carry `pending`, weak edges carry a `Lookup` and never wait |
|
|
367
|
+
| T7 | `start-after` has no counterpart: opening order is derived, not declared, and lives in `plan.groups[].order` |
|
|
368
|
+
| T8 | Provider selection happens at compile time, so there is no admission, candidate set or ambiguity at runtime — only whether the selected provider has a live instance |
|
|
369
|
+
| T13 | `requires-port` is split into hard and optional by `CompiledApplicationRequirementIR.optional`, since the compiler emits one kind for both |
|
|
370
|
+
| T14 | Port edges run through the port node: `provides-port` from the provider, `requires-port` and `absent-port` to the consumer |
|
|
371
|
+
| T15 | `absent-provider` is a node so an `absent-import` edge has an origin |
|
|
372
|
+
| T16 | A contribution is two edges: `owns` from the contributor and `contributes-to` to the target |
|
|
373
|
+
| T17 | Failures are the runtime's own error classes as a discriminated `subject` + `code` pair |
|
|
374
|
+
| T19 | Schema `opetope.devtools-graph/3` and `opetope.devtools-frame/3` |
|
|
375
|
+
| T23 | The hostile decoder is strict: unknown fields and kinds are rejected with the record, accessors are refused before being read, and the polluting keys always |
|
|
376
|
+
| T25 | The performance corpus is generated, deterministic by construction: 1 330 nodes / 5 008 edges and 26 331 / 70 040 |
|
|
377
|
+
| T26 | Tree structure is cached for the session and display resolved per rendered row; search, projection and layout are memoized on the store version |
|
|
378
|
+
| T28 | Phase 4 canary. A lone `when` flip emits one `contribution-change` frame and moves the contribution `published` ↔ `withheld`; closing a live application removes its instances and contributions. Both were broken and were fixed by D167; the canary tests are written so masking cannot recur |
|
|
379
|
+
| T29 | The consumed interface takes the ids the runtime declares. Brands are optional markers, so a plain `string` is accepted and no host carries a cast; the guarantee lives in the validators and the decoder |
|
|
380
|
+
| T31 | Close/detach/GC matrix: no leak in the package. Per-cycle growth settles, and the container that survives is held by one of React DOM's own module-level root slots — `rootWithNestedUpdates` or `rootWithPassiveNestedUpdates`; the observed retainer was the passive one, which exists only in the development build. Both hold a single container at a time, both sit outside devtools, and the panel is dev-only, so production never faces either |
|
|
381
|
+
| T33 | The detach returned by the attach is owned by the launcher that owns the execution: it runs before the close, fences a panel whose import lands late, and closes exactly the handle it installed |
|
|
382
|
+
| T34 | The decoder reads array entries by index through their own descriptors: an entry may be an accessor just as a field may, and `map` on the incoming array is the payload's method, not ours. The text budget counts UTF-8 bytes, bounded above and below by the code-unit length so the exact count stays off the hot path |
|
|
383
|
+
| — | Superseded: T2, T5, T9–T12, T18, T20–T22, T24, T27, T30, T32 described earlier taxonomies, budgets or open questions and are replaced by the rows above and by §2, §4 and §6 |
|
|
384
|
+
|
|
385
|
+
### 9.2 Panel
|
|
386
|
+
|
|
387
|
+
| № | Decision |
|
|
388
|
+
| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
389
|
+
| U11 | Virtualization thresholds are set rather than measured: 200 rows for lists and the inspector, 300 nodes for a worker layout |
|
|
390
|
+
| U12 | The lens clips its content; pan, wheel zoom, buttons and fit-to-view are the way to reach what is outside the frame |
|
|
391
|
+
| U13 | An ancestor breadcrumb navigates; the current node stays a label |
|
|
392
|
+
| U14 | Node cards are opaque and above the edges, so a line cannot cross a badge |
|
|
393
|
+
| U15 | Export delivers to the clipboard or a local file by capability and always reports the outcome |
|
|
394
|
+
| U16 | The panel offers no control action it cannot perform; the status bar states once that the control plane is off |
|
|
395
|
+
| U18 | A label is presentation metadata derived from a declaration: never identity, never authority, and never shown as a protocol field name |
|
|
396
|
+
| U19 | One collapsible legend for states, edges and badges |
|
|
397
|
+
| U20 | English and Russian, defaulting to the browser, switchable in the header; the English dictionary is the typed source of truth |
|
|
398
|
+
| U21 | The translation boundary: prose is translated, the protocol vocabulary is not, and protocol words are placed into translated sentences |
|
|
399
|
+
| U22 | The panel size cap is 20 kB brotli, raised from 15 with the measurement recorded; the headless consumer stays under 5 kB |
|
|
400
|
+
| U24 | Translation completeness is policed by a test that renders the Russian panel across every surface and fails on prose it does not recognise |
|
|
401
|
+
| U26 | Wheel zoom scales with the size of the gesture; buttons step 1.2×; `deltaMode` is normalized and one event is clamped |
|
|
402
|
+
| U27 | A drag moves the camera and selects nothing |
|
|
403
|
+
| U28 | Two tabs, `Graph` and `Timeline`; the former lenses become edge filter chips with four presets |
|
|
404
|
+
| U29 | Extension points is a two-step projection preset, not a lens: feature → contributions → targets, inverted when a target is selected |
|
|
405
|
+
| U30 | The manual `Graph`/`List` switch is gone; the list is the automatic form below 1100 px |
|
|
406
|
+
| U31 | The status bar keeps `frame`, `dropped` and `LIVE` visible and hides the panel's own diagnostics behind `⋯` |
|
|
407
|
+
| U32 | Control is limited to condition override, on the group node, through a separate dev-only write port with its own authority |
|
|
408
|
+
| U33 | The control plane, the legend, the exporter and the timeline load on demand, each with its own chunk, its own dictionary and its own size budget; the panel carries only what every session uses |
|
|
409
|
+
| U34 | The control surface is fetched when the selection holds something the port can address — a condition group with a condition, or a quarantined instance — and not merely because a write port exists |
|
|
410
|
+
| — | Superseded: U1–U10, U17, U23, U25 covered v1 glyphs, the canvas default, the search hit column and the naming of the extension-points preset, and are replaced by the rows above and by §4 |
|
|
411
|
+
|
|
412
|
+
The inspection graph/frame schema is `/3`, with optional `opetope.runtime-activity/1` snapshots. Within one session,
|
|
413
|
+
a frame without `activity` preserves the previous activity; a full snapshot/reset without it clears that observation
|
|
414
|
+
(D216). Activity-bearing frames replace the previous activity in full.
|
|
415
|
+
Use matching runtime/devtools versions: `/2` readers reject the new revision. Activity identifies the execution,
|
|
416
|
+
actual feature generation, physical Calls, exact current lane blockers, registered resource leases and load attempts.
|
|
417
|
+
Host demand and UI models are unknown; stream observation covers state, not physical load identities. `freshness`
|
|
418
|
+
and `truncated` distinguish a complete live view from a partial or detached one. A closed session is stale;
|
|
419
|
+
`closed: true` requires successful physical application drain. No control authority or product payload is added.
|
|
420
|
+
Activity output is bounded by record capacity. Snapshot collection still visits registered owners, executors and
|
|
421
|
+
resources, so capacity does not bound traversal cost. Collection stops once truncation is proven;
|
|
422
|
+
idle executors may still require traversal to establish completeness. Normal call dispatch allocates no diagnostic record with
|
|
423
|
+
observation disabled. Graph frames remain bounded by the existing ring capacity.
|