Skip to content

Module contract has no additivity policy, and foreign_methods[].service shipped without a refusal rule or fixture #857

Open

Description

@gael-obin

Follow-up from #856, raised by an independent platform review of the core#856 → cli#1196 → obin-ai/module-robin#563 chain. The field itself is well placed; two obligations it creates are unrecorded.

1. Adding a field to codefly/module-contract/v1 is forward-incompatible, and nothing says so

Parse is strict by design, and the design intent is a security property — contracts/module/contract.go:714-718:

Decoding is strict: an unknown field is an error rather than a silently ignored intention, which is what refuses a contract carrying a tenancy, a build digest or an identity it is not entitled to assert.

The consequence is that adding any field to the schema is forward-incompatible for every reader built before it, while the schema string stays v1 and therefore gives the reader no version-skew signal: the schema check at parse() passes and the document then fails as malformed.

Reproduced against module-robin's real module.contract.codefly.yaml (which carries service: on three foreign_methods entries), parsing the same file through each core pseudo-version:

=== core v0.19.7-0.20261026194550-a8599bd3991a (pre-#856) ===
REFUSED: the module contract carries a field no contract declares
  line 426: field service not found in type module.ForeignMethod
  line 431: field service not found in type module.ForeignMethod
  line 436: field service not found in type module.ForeignMethod

=== core v0.19.7-0.20261027183450-b71ee8cd6ed0 (#856 merged) ===
ACCEPTED, foreign methods: 3

docs/module-contract.md contains no versioning or additivity policy at all — grepped for additive, compatib, version step, unknown field and forward; the only hits are unknown-field refusal in unrelated sections.

Scope, measured rather than assumed. Only cli and core import core/contracts/module anywhere in the org; every obin-ai hit is a *_test.go. codefly-dev/authority has zero modulecontract.Parse/Load call sites in production or test code — it imports core/solutionhost for other reasons and modulecontract only for Operation* constants and in-memory Contract construction, neither of which the new field touches. That matters because all ten authority go.mod files, including the deployed local, hop and edge services, pin core …20261026092055, which is pre-#856.

So this is a rollout and documentation obligation on the CLI reader family, not a break in a deployed service. It still bites: a developer or a CI job on a CLI built against pre-#856 core hits field service not found at codefly run or gitops render, with nothing upstream warning them.

Closure: docs/module-contract.md states the additivity policy for codefly/module-contract/v1 — that fields may be added without a schema-string change, what that implies for readers, and the minimum core a reader needs for each added field (starting with service: → b71ee8cd).

Caveat on scope: code search only covers repos indexed and visible to the searching account.

2. The new field has no refusal rule and no accepted fixture

contracts/module/rules.go:95-100 names five foreign-method rules — foreign-method-name, -unique, -requires, -own-kind, -own-action — and each has a testdata/ fixture (foreign-method-declared-twice.yaml, foreign-method-requires-nothing.yaml, foreign-method-beyond-ceiling.yaml, foreign-method-other-kind.yaml, foreign-method-scope-without-action.yaml). None of those fixtures carries service:, and no rule constrains its shape.

This is conformance rather than safety: blank and whitespace selectors do fail closed downstream in the CLI. But this package declares itself home to every refusal rule, and its fixture corpus is what drives the independently released readers, so a field with neither is a gap in the corpus rather than a judgement that the field needs no rule.

Closure: an accepted fixture carrying service: so the corpus exercises round-tripping it, plus a shape rule if one is warranted.

Context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions