Lolly Beta
Launch App

Constraints, authority and local choice

This chapter is a draft for review, dated 2026-09-24.

A constraint is a rule the software enforces, not guidance a style guide offers (docs/constraints.md). Constraints can travel with a tool and can also be supplied by Lolly Work (D6). This chapter specifies how those sources compose into the one rule set an evaluation ran under, which this model calls the effective policy.

Three separate decisions are kept apart throughout: a creative rule, structural validity and a device grant. Rejecting an approved-font rule must never make an invalid graph valid, and it must never grant code a device power (R6). A single control labelled "accept constraints" must never combine brand acceptance, code trust and device permission (R6).

This chapter restates plan section 10 in normative form, with the correction the review recorded as C4 and the correction it recorded as C5. The pipeline step that determines effective policy is specified in Evaluation and receipts. The suites and reports that measure a result are specified in Conformance and fidelity. Lolly Work is a separate repository, and a lolly-work/ path in this chapter is a file in that checkout rather than in this tree.

What a rule is

A rule is a typed predicate with an identity, so that a finding can name it and a waiver can be recorded against it (R6).

draft shape

  RuleV1
    id              permanent within its issuer's namespace
    revision        changes when the predicate or its parameters change
    issuer          who placed the rule: an instance overlay, the tool author, a person
    scope           the inputs, rows, collections or output targets it applies to
    class           advisory | waivable-creative | structural
    severity        how a violation is reported when the rule is active
    phase           the validation phase it runs in, per the evaluation pipeline
    predicate       an ExpressionV1 (R2, plan 194's JSON AST, defined there and not built),
                    evaluated under its budget
    repair?         guidance for the patch a repair would propose

This is a draft shape, not a frozen type. Nothing is added to packages/core or schemas/ in this wave, and the plan's section 18 orders the counterexample fixtures before any of it is frozen.

Every enforced rule and every exception must name its issuer (R6). Lolly Work already carries that attribution where an operator supplied it. resolveInputAccess in lolly-work/server/src/policy/overlay.ts copies the overlay's display name onto by and the rule author's text onto reason on every matched rule. Both stay absent when the overlay is unnamed or the rule carries no reason. The question "why is this locked" is answered by the same resolution that locked it, so the answer and the lock can never disagree. A Kubernetes reader will recognise the arrangement: an issuer here does what a field manager does there (D13). This model closes the remaining case. An issuer is required, not optional, so an unnamed overlay must never lock an input while naming nobody.

Three rule classes

A rule must declare one of three classes (R6, C4). The class decides who, if anyone, may set the rule aside.

ClassMeaningWho may set it aside
AdvisoryA preference. A violation is reported and never stops the operation.Nobody needs to: it does not block.
Waivable creativeAn authored creative rule, such as an approved font or a palette.The authority for this execution, recorded as an exception.
StructuralStructural validity, a required input or a device grant.Nobody. A waiver against a structural rule must be refused.

The review found the plan alternating between "the stronger rule wins" and "both rules conflict" without saying which case applied (C4). The class is what decides. A waivable creative rule may be deactivated by a recorded waiver. A structural rule may not, whatever the context (R6).

The three layers

Rules reach an evaluation from three layers. Each layer must name the issuer of every rule it contributes (R6).

Authority decided first, then the applicable exceptions, then the active rule set composed from the instance overlay, the authored rules and the local decisions, then evaluation, with the issuer carried on every edge.Open this in Diagram Builder

1. Instance overlay, supplied by Lolly Work. lolly-work/server/src/policy/overlay.ts defines AccessLevel as editable, choice, locked or hidden per input and group, with an optional fixed value, an allowed set and a written reason. The same overlay carries an enforce block with formats, c2pa, watermark and an escalation string that points at an approval chain. lolly-work/server/src/policy/org-config.ts resolves that overlay per caller into OrgConfigPayload.tools, where a hidden tool is absent rather than marked, and folds the result into one policyVersion string that behaves as an ETag. checkParams in lolly-work/server/src/policy/overlay.ts refuses a locked, hidden or out-of-set parameter at the render route (lolly-work/server/src/render/pipeline.ts). The refusal carries the same by and reason the shell shows, so the overlay is enforcement and not presentation.

2. Authored rules, travelling with the tool. packages/core/src/design-tool-v1.ts defines DesignInputV1.approved as an approved value list, fixedInputs on each DesignChoiceV1 option as the inputs that option owns, and DesignTextRuleV1 and DesignImageRuleV1 as per-property rules. The same module reports an ownership finding when an option and a reader input both write one property, which is the one-writer-per-property rule. Catalog and material locks belong to this layer too: brandLock on a tokens asset in schemas/asset.schema.json makes a brand authoritative and disables brand customisation. A hosted design system carries the material form of the same lock. DesignSystemRecord.locked in shells/web/src/lib/design-system/registry.ts is set from the catalog's brandLock, and it is what makes hosted colours, type and logos read-only. That lock is why "Make an editable copy" is a separate act rather than an edit.

3. Local decisions. A person's recorded waivers and exceptions. engine/src/session-record.ts already carries rightsDecisions on the instance at format 4, and packages/core/src/rights-v1.ts defines the decision it holds.

A layer must never be read as a precedence order on its own (C4). The order in which rules are composed is specified next, and authority comes first.

The resolution order

Effective policy resolves in four steps, in this order, and a host must never reorder them (R6, C4):

  1. Authority. Decide who holds acceptance authority for this execution. In governed execution it is the Lolly Work administrator for the brand. Otherwise it is the local person using the client (D12).
  2. Exceptions. Collect the exceptions that authority permits, including any recorded local waiver whose facts still match.
  3. Active set. Compose the active rule set from the rule-bearing layers, the instance overlay and the authored rules, then deactivate each rule a permitted exception names. The local-decision layer contributes no rules of its own: it acts here, through the exceptions collected in step 2.
  4. Evaluate. Evaluate the active rules and report the findings.

Composing first and consulting the person afterwards was the fault the review named (C4). A recorded rejection that never reaches the active set is a receipt entry and nothing more, which contradicts D7.

Local waivers in unmanaged execution

In unmanaged execution an authorised local waiver must deactivate the named waivable creative rule, for the named instance and for the fingerprinted facts (R6, C4). The original rule must be preserved in the definition, and the waiver must be recorded on the instance.

The recording pattern already exists. packages/core/src/rights-v1.ts defines RightsDecisionV1 with a fingerprint field, and RightsEvaluationV1 carries a situation hash taken over the same facts with the decisions left out. A decision applies only while the facts still hash to that value. A changed source or format retires the choice rather than letting it answer for something it was never about. engine/src/runtime.ts exposes setRightsDecision and setRightsDecisions, and engine/src/session-record.ts persists the list.

A local waiver must follow the same three rules (R6):

Measured conformance to the unmodified rule must read failed or excepted, never passed (R6, C4). The receipt must list the rules a run bypassed (R6), among the fields R15 requires of it. A waiver changes what this evaluation enforced, and it never changes what a suite measured.

A local waiver must never deactivate a structural rule or grant a device capability (R6). Device capabilities stay in the capabilities list of schemas/tool.schema.json and are granted by the host, not by a rule decision.

Governed exceptions

In governed execution only overlay-permitted exceptions apply, and the document must never grant itself one (R6). A rule that arrives with the tool must never authorise its own bypass. A person editing under an overlay that forbids an exception must never be granted one.

Where the overlay carries an approval chain, the exception is a request rather than a decision. lolly-work/server/src/approvals/engine.ts defines a chain as an ordered list of steps. Each step has an eligible group and a step rule of any, a quorum or all. Each act is evaluated with the actor's identity, so the submitter can never clear their own request. An approval state moves through submitted, in_review and then one of approved, rejected or withdrawn. An exception granted this way records the approval it came from.

Conflicts stop the operation

When two active required predicates conflict, the affected operation must stop, and the report must carry both rule ids and the affected values (R6). A host must never choose one of the two silently, and it must never invent a repair to reconcile them.

This is the case the class table does not cover, because neither rule is waivable and neither is advisory. Stopping is the outcome, and the outcome carries a blocked termination as specified in Operations and outcomes.

The context table

ContextBehaviour
Local person accepts the constraintsEvaluate against them and expose the findings.
Local person rejects or relaxes a constraintDeactivate the named waivable creative rule for this instance and these facts. Keep the original definition. Record the waiver against the rule id and the fingerprint on the instance. A changed fact needs a fresh decision. The receipt lists the bypassed rules.
Governed executionApply the authenticated overlay and the exceptions it permits. The document must never grant itself an exemption.
Required rules conflictReport both rule ids, the affected values and the conflict. Stop the affected operation.
Governed client offlineKeep enforcing the policy the client holds. Report the four facts below separately. Apply any explicit freshness rule the policy carries. Keep cached work usable.

A governed client that loses its connection

A governed client that loses its connection must keep enforcing the policy it holds, and nothing relaxes (R7). The review found the plan describing offline behaviour the design system code does not have, so the four facts below are stated as four separate answers rather than one status (C5).

FactWhat it answersWhere the tree answers it today
Refresh dueHas the check interval lapsedORG_CONFIG_TTL_MS in shells/web/src/org/index.ts for the organisation policy, against the policyVersion ETag (lolly-work/server/src/policy/org-config.ts); HOSTED_CHECK_INTERVAL_MS in shells/web/src/lib/design-system/hosted.ts for a hosted design system
Update knownWas a change detected that could not be brought downThe stale outcome of refreshHostedDesignSystem
ValidityIs the held policy still valid on its own termsRepresented today only as a cache horizon, and the wrong way round: ORG_CONFIG_TTL_MS (24 hours) in shells/web/src/org/index.ts makes readCachedOrgConfig evict a held copy past that age, after which the boot path fails closed. This chapter's rule replaces that horizon with an explicit validity rule.
AttestationWhen was the policy last attested by its issuerNot represented today; policyVersion identifies the policy, not its attestation

refreshHostedDesignSystem in shells/web/src/lib/design-system/hosted.ts returns unreachable when the host cannot be reached, and it returns stale only after a detected change failed to import. checkHostedDesignSystems skips the sweep when the browser reports itself offline. A refresh interval that has lapsed must never be read as an expired policy credential (C5).

Today the shell does not meet this rule. shells/web/src/org/index.ts serves a cached organisation policy only inside ORG_CONFIG_TTL_MS. Past 24 hours it drops the copy, sets no policy at all and clamps the export and input seams closed. Enforcing the held policy instead, and reporting the four facts, is a change this model requires. Plan section 17's 25-hour offline case is where it is measured.

A freshness deadline for a governed-claim export must be an explicit policy rule, with an issuer, a validity interval and a stated behaviour when freshness cannot be established (R7). With no such rule, the held policy must be enforced, and the receipt must say which policy version was evaluated and when it was last attested (R15 for the policy version, Q3's default for the attestation).

Cached material must stay usable (R7). A hosted record keeps its core material on the device and records lastSyncedAt (shells/web/src/lib/design-system/hosted.ts). The device keeps working when the host has gone away, and the row reports how long ago it last synced. A receipt must never describe a held policy as current online approval (C5).

Leave and Make an editable copy

Leave and "Make an editable copy" are two actions with different results, and this model must never merge them (R7, C5).

leaveInstance in shells/web/src/lib/instance-leave.ts removes what the organisation supplied: its cached configuration, its pack and the tools that pack installed, the install identity this device spoke while enrolled and the native shell session. For a hosted design system the same act is scoped to the record: plan 186 section 3.6 defines Leave there as "Remove this design system", running this sweep plus the namespace delete. What the person made stays: their sessions, images, preferences and any tool they sideloaded themselves. countSessionsUsingInstanceTools in the same module reports how many saved sessions belong to a tool the pack installed, so the exit dialog can say those sessions will not open until the device reconnects. Leave must never be turned into a copy operation (C5).

"Make an editable copy" is a separate, explicit action. createDesignSystem in shells/web/src/lib/design-system/manage.ts writes a local record, and shells/web/src/lib/design-system/registry.ts defines that source union. Where the copy was made from a hosted or local system, the record points at it in forkedFrom, with a version where the source carried one. A copy of the shipped system records no source today, and this model requires one. A copy made this way must state its dependency set, and it must carry no inherited governance claim (R7). The copy is the person's material from that moment, and the organisation's rules no longer travel with it.

Repair

Repair is validate first, then propose (R6). A host must validate, then propose an explicit patch, and never mutate authored content to satisfy a rule without saying so.

A conflict between two required rules must never produce an invented repair (R6). The operation stops instead, and a person decides.

Measured conformance and contextual acceptance

Two answers must stay separate, always (R8):

  1. Measured conformance. The suite, its released version, the reference environment, the comparator version and the per-feature results.
  2. Contextual acceptance. Whether the relevant authority accepts that result, with each exception named.

An authority may select a suite, require stricter checks, approve references or accept an exception (R8). None of those decisions may change the meaning of a suite version already published under another identity. A local waiver is an acceptance decision and never a measurement, which is why measured conformance to the waived rule still reads failed or excepted (R6, C4).

Lolly Work's planned effective-schema projection (Work plan 42, not built) and the engine's documentSchema (engine/src/document-api.ts) are the same projection of policy onto a schema. The model must specify one function for both (plan section 10.2), so that a locked input reads the same way in the shell, the command line and the MCP surface.

Open points

The other open questions are carried, with their plan section 16 defaults, in other chapters: Q1 and Q5 in Conformance and fidelity, Q4 in Operations and outcomes and Q6 in Status and open points. All six remain Andy's to answer. A default is what drafting proceeds on, not an answer.

Precedents

In this repository:

In Lolly Work, which is a separate repository and not part of this tree: