8000 Per-instance port behavior via node metadata (work document #44) by bergie · Pull Request #1098 · noflo/noflo · GitHub
[go: up one dir, main page]

Skip to content

Conversation

@bergie
@bergie bergie commented Oct 18, 2026 •
Copy link
Copy Markdown
Member

The noflo.* node-metadata keys apply port-behavior overrides to a component instance after instantiation and before wiring, completing the #594 lineage: a graph can now reuse an ecosystem component while tweaking the knobs its graph needs.

  • noflo.control: string[] — the instance's complete gate-inport set, replacing the declaration: listed names become gates (standing, non-consumingly-read configuration whose arrivals re-fire pending work per scope), unlisted former gates become direct firing edges (replace semantics makes promotion and demotion both expressible)
  • noflo.scoped: object — port-name to boolean, with the buffer structures re-prepared on flip (the helper runs pre-wiring, so there is no buffered state to migrate)
  • noflo.forwardBrackets: object — per-inport rule replacement, including the framing-termination empty rule; forwarded brackets attach to the ports the component's actual sends go through, so an override reroutes the framing when the component sends to the overridden target

Applied by the Network after the port-name stamping, before subscribeNode. Overrides are per-instance (the loader instantiates one component per node, so other instances keep their declarations), validated loudly (unknown noflo.* keys — the namespace is core-owned — nonexistent ports, non-string names, and mistyped values are build errors named with the node id), and the zero-regression posture holds: a node without noflo.* metadata is untouched.

The schema touchpoint: the keys are declared by the noflo/port-behavior metadata schema — the noflo-namespace catalog entry shipped with the publish-time machinery (work documents #37/#25) — and this module is the runtime consumer, validating the subset a running network can check. No catalog artifact ships in this slice; the publish gate (wd #25) is where the namespace reservation and the opt-in composition enforcement land.

Spec: the knob matrix from the work document — promote data to gate, demote gate to data, scoped flips in both directions with per-scope pairing, forwarding replaced and terminated per instance, the zero-regression posture pinned by a second instance of the same component, the validation error class, and the network-level wiring with the node-id error surface.

Fixes #594

The noflo.* node-metadata keys apply port-behavior overrides to a
component instance after instantiation and before wiring, completing
the #594 lineage: a graph can now reuse an ecosystem
component while tweaking the knobs its graph needs.

- noflo.control: string[] — the instance's complete gate-inport set,
  replacing the declaration: listed names become gates (standing,
  non-consumingly-read configuration whose arrivals re-fire pending
  work per scope), unlisted former gates become direct firing edges
  (replace semantics makes promotion and demotion both expressible)
- noflo.scoped: object — port-name to boolean, with the buffer
  structures re-prepared on flip (the helper runs pre-wiring, so there
  is no buffered state to migrate)
- noflo.forwardBrackets: object — per-inport rule replacement,
  including the framing-termination empty rule; forwarded brackets
  attach to the ports the component's actual sends go through, so an
  override reroutes the framing when the component sends to the
  overridden target

Applied by the Network after the port-name stamping, before
subscribeNode. Overrides are per-instance (the loader instantiates one
component per node, so other instances keep their declarations),
validated loudly (unknown noflo.* keys — the namespace is core-owned —
nonexistent ports, non-string names, and mistyped values are build
errors named with the node id), and the zero-regression posture holds:
a node without noflo.* metadata is untouched.

The schema touchpoint: the keys are declared by the noflo/port-behavior
metadata schema — the noflo-namespace catalog entry shipped with the
publish-time machinery (work documents #37/#25) — and this module is
the runtime consumer, validating the subset a running network can
check. No catalog artifact ships in this slice; the publish gate
(wd #25) is where the namespace reservation and the opt-in composition
enforcement land.

Spec: the knob matrix from the work document — promote data to gate,
demote gate to data, scoped flips in both directions with per-scope
pairing, forwarding replaced and terminated per instance, the
zero-regression posture pinned by a second instance of the same
component, the validation error class, and the network-level wiring
with the node-id error surface
- Formatted: the four touched files now pass biome (the CI lint error)
- Subgraph nodes are excluded explicitly: a noflo.* override on a
  subgraph node is a build error naming the limitation — the relay
  semantics for exported ports were not designed against per-instance
  port knobs, and the exclusion keeps the deterministic posture. The
  guard fires only when overrides are present (the first ordering
  threw for every subgraph node, breaking the whole subgraph suite)
- All three knob helpers validate-everything-first, then apply
  atomically: a later invalid entry cannot leave earlier ports
  half-flipped (applyPortMetadata is public API)
- The pre-wiring contract is in the JSDoc: a noflo.scoped flip
  re-prepares buffer structures and discards buffered state — direct
  API callers must honor it
- noflo.control tightened: addressable inports are rejected (the gate
  model does not scan addressable buffers — a promoted addressable port
  would strand its arrivals); noflo.scoped tightened to inports: the
  outport-side flip interacts with the ordered-output and forwarding
  machinery and its semantics need their own design pass (the
  outport-flip test surfaced load going negative through the ordered
  machinery under a scope-mismatched send). Both rejections pinned by
  spec; the outport-side scoped test replaced by its rejection pin
- The outport-side scoped decision recorded in work document #44
… rejections documented

- The subgraph-exclusion test uses an empty inner graph: no inner loads
  run, so nothing leaks past the assertion (the round-two async-leak
  finding — the Subgraph's internal network was mid-build when the
  wrapper threw, rejecting unhandled)
- applyScoped validates everything before applying anything: the
  boolean and existence checks moved into the validation pass, making
  the flips atomic like applyControl (the round-two finding —
  {a: false, b: mistyped} left a flipped)
- The three rejections documented in the CHANGELOG and the migration
  guide: the subgraph exclusion, the inports-only scoped knob, and the
  addressable gate-promotion rejection
- Formatted (the round-two format findings)
@bergie
bergie marked this pull request as ready for review October 18, 2026 00:48
…alf of the knob story

The runtime's schema table (work document #37 phase 2) loads installed
packages' noflo.json schemas tables at startup and serves the bodies by
digest through the Two-Step Cache (0x29, with mandatory client-side
digest verification and closure completeness) — so shipping the
artifact and pinning it in the manifest is what makes the noflo.*
vocabulary visible to protocol clients: fbp-client and noflo-ui fetch
the catalog and the per-component composed config schemas, verify
digests transitively, and know the vocabulary without assuming.

- schemas/port-behavior.json: the noflo-namespace catalog — the three
  knobs with their schemas, class config, kind node; the outport-side
  scoped flip documented as not overridable
- noflo.json: the package manifest's schemas table, pinning the
  artifact's digest (computed via the fbp-protocol digestSchema over
  the canonical form)
- The package files field ships noflo.json and schemas/
- PortMetadata's schema touchpoint updated: the artifact ships, the
  manifest pins it, the runtime's table serves it
- The runtime spec pins the client story end to end: loading the
  @noflo/noflo package manifest into a SchemaTable, the digest pins
  holding, and the served body byte-identical to the shipped artifact

The deployment wiring (which installed packages a given runtime scans)
stays the deployer's policy per the work document #24 ladder; the
per-component composed config schemas (the allOf composition, work
document #37) flow from the publish-time manifests as components opt
in
The shipped catalogs' style was enforced only by hand — the format and
lint scripts' globs did not include the schemas directories, so
inconsistencies between sibling artifacts could not be flagged by CI.
The root format script and the packages carrying schemas directories
(assembly, fbp-protocol, noflo) now cover them.

The reformat of the noflo port-behavior artifact is the kinds arrays
collapsing to the siblings' single-line style; the digest is over the
canonical form and the manifest pin still matches.

Also: the CHANGELOG amendment for the shipped artifact (the round-four
finding — the packaging-visible change: the tarball now ships
noflo.json and schemas/)
@bergie
bergie merged commit be2426b into master Oct 18, 2026
8 checks passed
@bergie
bergie deleted the port-behavior-overrides branch October 18, 2026 01:18
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.

Helper for tweaking component behavior via node metadata

1 participant

0