You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR #59 wrote docs/cmf-extensions.md, which catalogs the keys each of the twelve extension slots emits. What it does not do is check that the catalog is complete, and it is not: SecurityExtension.objects and SecurityExtension.data are unbridged and appear nowhere in the document. Populating both and running extract_extensions emits 19 keys, none referencing either.
That is the failure mode this issue closes. The catalog is hand-maintained, so it drifts by the next extension, and constants.rs already flags the same risk in its own header. A test that walks a fully populated Extensions and asserts the emitted key set is what makes the document stay true, and it is also the machine-readable vocabulary the absent-key contract issue needs before it can start.
The remaining unbridged fields below are the sweep #7 never finished. #7 fixed the shape problem for roles and permissions, because a boolean per role answers "does alice have hr" but cannot answer "what roles does alice have". The same pass over the other ten slots did not happen. Each field here becomes either a bridged key or a recorded decision with a reason.
Fields to resolve
SecurityExtension.objects and .data. Unbridged and undocumented. DataPolicy carries apply_labels, allowed_actions, denied_actions and retention, which is a field built to express data policy that the policy language cannot read. Check whether praxis reads them off the typed extension first; if it does, they are host-only by design and that is the decision.
DelegationExtension.chain. Nothing from it is bridged, so delegation.depth <= 2 is expressible but "deny if any hop granted write:payroll" is not. This does not need an array value type: union sets delegation.scopes and delegation.audiences plus an any-flag delegation.from_cache cover the common intents. Largest gap on the list for a delegation-aware engine, and possibly its own issue.
MCP annotations on ToolMetadata, ResourceMetadata and PromptMetadata. Skipped as free-form, but framework.metadata.*, custom.*, claim.* and client.claim.* are all free-form maps that go through payload::walk. Three walked, three not, no stated principle. Annotations are where readOnlyHint and destructiveHint live, which is what a tool-gating rule wants.
AgentExtension.conversation.history. Skipping the transcript is right. There is no agent.conversation.history_len either, and turn-count gating is an ordinary thing to want.
WorkloadIdentity.attested_at. Deferred for "no datetime type" while request.timestamp and completion.created_at ship as plain strings. Either decision is fine, it should be one decision.
ToolMetadata.input_schema / output_schema, PromptMetadata.arguments. Plausibly out of scope, currently undocumented rather than decided.
client.teams has no flattened booleans.subject.teams writes the set and team.<name> (security.rs:115-120); client.teams writes only the set (:188). Five collections get both projections and nine get only the set, and nothing states the rule, so require(tag.pii) is silently false forever with no way for an author to tell that is by design. The recommendation is to keep the shape and write the rule down: the set is the primary form, the five flattened collections exist only because that DSL idiom predates the sets, and no more are added. Mirroring booleans across the other nine would double the key count per request to buy sugar over contains. client.teams is the one worth a second look, since the asymmetry inside a single namespace pair reads as an oversight rather than a decision.
Also in scope
capability_namespaces.rs is the crate's existing capability-to-bag-prefix table and it disagrees with the new catalog in two places. CAP_READ_LABELS maps to &[] although security.labels is written. CAP_READ_WORKLOAD maps to BAG_WORKLOAD_PREFIX ("workload."), which nothing writes and which has exactly one use in the tree, that table entry; the bridge writes caller_workload.* and this_workload.*. Whatever binds the catalog to the bridge should cover this table too, so the crate stops carrying three hand-maintained key lists that can disagree.
Acceptance criteria
A test walks a fully populated Extensions through extract_extensions and asserts the exact emitted key set and value types, so a new extension field cannot land unbridged and unnoticed
docs/cmf-extensions.md is generated from, or checked against, whatever that test asserts, rather than maintained by hand alongside it
Every field in "Fields to resolve" is either bridged with a key and type in the catalog, or carries a recorded decision naming the reason
security.objects and security.data specifically are confirmed host-only by design or bridged
The set-only versus set-plus-booleans rule is stated in docs/cmf-extensions.md, naming the five collections that carry booleans and the nine that do not, and saying the list is closed
client.teams is decided either way: flattened to client.team.<name> to match subject.teams, or recorded as set-only with the reason
capability_namespaces.rs agrees with the emitted key set, enforced by the same test rather than by review
BAG_WORKLOAD_PREFIX is removed or corrected, and this_workload.* is reachable through read_workload
The resulting key universe is exported in a form the Cedar schema generator and the CEL load-time check can consume
Description
PR #59 wrote
docs/cmf-extensions.md, which catalogs the keys each of the twelve extension slots emits. What it does not do is check that the catalog is complete, and it is not:SecurityExtension.objectsandSecurityExtension.dataare unbridged and appear nowhere in the document. Populating both and runningextract_extensionsemits 19 keys, none referencing either.That is the failure mode this issue closes. The catalog is hand-maintained, so it drifts by the next extension, and
constants.rsalready flags the same risk in its own header. A test that walks a fully populatedExtensionsand asserts the emitted key set is what makes the document stay true, and it is also the machine-readable vocabulary the absent-key contract issue needs before it can start.The remaining unbridged fields below are the sweep #7 never finished. #7 fixed the shape problem for roles and permissions, because a boolean per role answers "does alice have hr" but cannot answer "what roles does alice have". The same pass over the other ten slots did not happen. Each field here becomes either a bridged key or a recorded decision with a reason.
Fields to resolve
SecurityExtension.objectsand.data. Unbridged and undocumented.DataPolicycarriesapply_labels,allowed_actions,denied_actionsandretention, which is a field built to express data policy that the policy language cannot read. Check whether praxis reads them off the typed extension first; if it does, they are host-only by design and that is the decision.DelegationExtension.chain. Nothing from it is bridged, sodelegation.depth <= 2is expressible but "deny if any hop grantedwrite:payroll" is not. This does not need an array value type: union setsdelegation.scopesanddelegation.audiencesplus an any-flagdelegation.from_cachecover the common intents. Largest gap on the list for a delegation-aware engine, and possibly its own issue.MCP
annotationsonToolMetadata,ResourceMetadataandPromptMetadata. Skipped as free-form, butframework.metadata.*,custom.*,claim.*andclient.claim.*are all free-form maps that go throughpayload::walk. Three walked, three not, no stated principle. Annotations are wherereadOnlyHintanddestructiveHintlive, which is what a tool-gating rule wants.AgentExtension.conversation.history. Skipping the transcript is right. There is noagent.conversation.history_leneither, and turn-count gating is an ordinary thing to want.WorkloadIdentity.attested_at. Deferred for "no datetime type" whilerequest.timestampandcompletion.created_atship as plain strings. Either decision is fine, it should be one decision.ToolMetadata.input_schema/output_schema,PromptMetadata.arguments. Plausibly out of scope, currently undocumented rather than decided.client.teamshas no flattened booleans.subject.teamswrites the set andteam.<name>(security.rs:115-120);client.teamswrites only the set (:188). Five collections get both projections and nine get only the set, and nothing states the rule, sorequire(tag.pii)is silently false forever with no way for an author to tell that is by design. The recommendation is to keep the shape and write the rule down: the set is the primary form, the five flattened collections exist only because that DSL idiom predates the sets, and no more are added. Mirroring booleans across the other nine would double the key count per request to buy sugar overcontains.client.teamsis the one worth a second look, since the asymmetry inside a single namespace pair reads as an oversight rather than a decision.Also in scope
capability_namespaces.rsis the crate's existing capability-to-bag-prefix table and it disagrees with the new catalog in two places.CAP_READ_LABELSmaps to&[]althoughsecurity.labelsis written.CAP_READ_WORKLOADmaps toBAG_WORKLOAD_PREFIX("workload."), which nothing writes and which has exactly one use in the tree, that table entry; the bridge writescaller_workload.*andthis_workload.*. Whatever binds the catalog to the bridge should cover this table too, so the crate stops carrying three hand-maintained key lists that can disagree.Acceptance criteria
Extensionsthroughextract_extensionsand asserts the exact emitted key set and value types, so a new extension field cannot land unbridged and unnoticeddocs/cmf-extensions.mdis generated from, or checked against, whatever that test asserts, rather than maintained by hand alongside itsecurity.objectsandsecurity.dataspecifically are confirmed host-only by design or bridgeddocs/cmf-extensions.md, naming the five collections that carry booleans and the nine that do not, and saying the list is closedclient.teamsis decided either way: flattened toclient.team.<name>to matchsubject.teams, or recorded as set-only with the reasoncapability_namespaces.rsagrees with the emitted key set, enforced by the same test rather than by reviewBAG_WORKLOAD_PREFIXis removed or corrected, andthis_workload.*is reachable throughread_workload