Skip to content

facetAllows has no slug-level facet, and module capabilities do not inherit exposure enforcement #169

Description

@JohnD-EE

Filed from Reclaim Your Week, Daybreak's first leaf. Two halves of one gap, found together.

1. Module capabilities do not inherit exposure enforcement

facetAllows is called by the framework's own fill_slot and get_state and by nothing else. BaseCapability does nothing with context.customConfig.

So a leaf that declares a capability on its own ModuleDefinition and grants it an ExposureConfig gets a config that silently enforces nothing. The grant looks identical to one that works.

We believed for a year that our write allowlist was enforced twice — once as data on the grant, once in code. It was enforced once, in code. Nothing was actually broken, because the code layer is the one that ran, but the invariant documenting it was wrong, and worse: our guard test asserted against a hand-written mirror of facetAllows rather than the function itself, so it passed while proving only that the data said what we meant.

The docs read as though the enforcement is automatic, which is how we got it wrong. CapabilityContext.customConfig is even documented as "a capability can read its own per-binding configuration inside execute()" — accurate, but it reads as a convenience rather than as the only thing that will ever apply it.

Suggested shape: a BaseModuleCapability, or a withExposure() wrapper, that applies the grant's facets before execute. Failing that, a sentence in BaseCapability saying the binding config is inert unless the capability applies it itself.

2. facetSchema cannot express a slug-level rule

facetSchema is .strict() on { groups, scopes }. A slot group that legitimately mixes machine-computed lanes with user self-reports cannot express its real permission as data.

Ours does exactly that: one group holds both per-bucket totals computed from an uploaded file (which a model must never write) and the answers to two questions the source tells the agent to ask before any file exists. We had to permit the whole group on the grant and keep the computed lanes shut in code — the two layers deliberately not identical, which is a thing that will read as a bug to the next person unless they find the comment.

Any facilitation module with a computed branch beside a discursive one hits this.

Suggested shape: slots?: string[] and/or denySlots?: string[] on the facet.

Delegate-when-it-lands

The leaf moves its slug allowlist onto the grant and deletes the code-side special case, and drops the parse-and-check block from its capability once BaseCapability applies facets.

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

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions