Specification v0.1 · open · vendor-neutral

Changelog

What changed in each version of the specification, as listed at the end of the specification itself.

Follow changes

Subscribe to the Atom feed to get new versions in your feed reader.

Atom feed

v0.1.1

In the specification

Corrections and a security fix. The type of every format stays the same. Three things can change an outcome, each named below: a noncharacter is no longer accepted in a new document, a leap second in an audit entry stops verifying in the reference verifier, and a signature under an Ed25519 key of small order stops verifying.

  • Correction noncharacters (Section 14, a new kind of change, and data/corrections-v0.json): RFC 7493 forbids noncharacters in member names and string values, and Section 3.1 item 1 refers to it, but the list in that item did not name them and the reference implementation accepted them. Item 1 now names them, and Section 9.1 names input that is not I-JSON among what the log cannot record. A new entry MUST NOT contain one. A verifier keeps accepting them in existing audit logs, so that no unaltered log becomes invalid; the outcome of the verification does not change. A stored document with a noncharacter is attributed as before (Section 4.3). No valid mandate changes: the free-form strings of a mandate are displayed text, which excludes noncharacters already, or printable ASCII. New conformance cases a213 to a220, i158 to i163, g24, s29, d52; beyond the correction, they cover duplicate names after escapes, every form of a lone surrogate, the legal pair and the number examples of RFC 7493.
  • Audit entries: a leap second in recorded_at, request.time or approval.at is invalid, as Section 3.1 item 0 says and the schema's description already referred to. The pattern in both schemas now excludes it. The reference verifier accepted it; spec-ts did not. This is not a correction (Section 14), because implementations did not read it alike, so a log with such an entry stops verifying in the reference verifier. No writer I know of produces one.
  • A seq that is no integer, however close, gives broken_at 0 (Section 9.4); the reference verifier read it through floating point. A zero is zero whatever its exponent (Section 3.2), also beyond the range of a machine integer: such documents become valid in the reference implementation, as Section 14 allows.
  • Test interface: a time that does not fit the grammar of RFC 3339 is not readable (Section 10.2). The reference harness and its checkpoint signing accepted offsets such as +24:00.
  • New conformance cases a221 to a228, c243 to c251, d53, i164 to i175, which also cover bidirectional controls and separators in displayed text, field by field.
  • Security (GHSA-grm8-w7m9-fxj9), Section 7.1: an Ed25519 public key of small order is rejected, because with it a signature verifies for every message of a class; the member names of a JWK and a JWK Set are case-sensitive, x and y allow no other character, and a member d with any value is a private key. One such key makes the whole key set unusable. Section 10.2: verify_signed with such a key set answers {"valid": false}. This can change an outcome: a signed mandate or a checkpoint verified with a key of small order stops verifying. That is the purpose, since anyone could have produced those signatures. A key that combines a normal point with one of small order stays accepted: only its holder can sign with it, so nothing can be forged. New conformance cases g25 to g35; checkpoints are covered by a test of the reference code.

v0.1.0

In the specification

First release without a pre-release suffix. The v0 formats (mandate, audit entry, audit checkpoint) and the vocabulary v0 are frozen from this release on (Section 14). The entries below are every change since the rename, through the pre-releases v0.1.0-alpha.1 to v0.1.0-rc.5 and the changes after v0.1.0-rc.5; v0.1.0 itself changes no rule, format or conformance case (conformance manifest of commit 116d009).

Renamed: the specification formerly published as mandate-spec is now the Home-Mandate Specification; repository github.com/home-mandate/spec, Go module github.com/home-mandate/spec, root package spec. Versions start again at v0.1.0-alpha.1. The releases of mandate-spec (up to v0.2.0-alpha.5) remain available from the Go module proxy under the module path github.com/mandate-spec/mandate-spec; their changes are listed below.

Audit mode (Sections 2, 3, 3.1, 3.2, 3.5, 4, 4.1, 4.4, 4.6, 6, 8, 9.1, 9.2, 9.4, 10, 11, 12, 13):

  • New optional member enforce of the mandate, only ever false (true, a string, a number or null are invalid, and a rule has no enforce). A mandate with enforce: false is in audit mode: a deny that results from its rules (no_match or a deny rule) for an action that is not critical is evaluated and recorded but not enforced; the PEP executes the request as if it were allowed. Critical actions, ask, allow, every denial before the rules (invalid mandate or request, unknown resource, category or action, revoked, not yet valid, expired) and every denial outside the evaluation (rate limit, emergency stop, authentication, approval) are enforced as before. The mandate type stays https://home-mandate.org/mandate/v0. Compatible: every mandate that was valid stays valid, is evaluated the same way and keeps its digest.
  • The result of the evaluation carries enforced: false for such a deny; the decision stays deny. In AuthZEN it is a member of the response context (Section 6); a PEP that does not know it enforces the denial. In the test interface it is a member of the evaluate and select response and compared over HTTP (Section 10); the conformance cases have the new field enforced.
  • Audit entry: evaluation.enforced (only false, only for a decision with evaluation.decision: deny and reason no_match or rule); only with it may a deny be executed or failed, and it is never denied unless it also carries confirm (see "Audit mode and ask" below). The audit type stays https://home-mandate.org/audit/v0. Compatible for logs: every entry that was valid stays valid. A verifier that does not know the member rejects a log that contains it (Section 14, unknown members).
  • With evaluation.enforced: false, an entry names request with the category of the resource; it is invalid for a resource marked critical (except for read), and a verifier MUST reject it for an action that is critical in the vocabulary or not in it (Sections 9.1, 9.4); request.resource.critical is then required, true or false, so that the marking cannot be left out. The reference verifier (audit) checks this against vocabulary v0 and against extension vocabularies it is given (audit.Options.Vocabularies, audit.VerifyWith, audit.VerifyJSONLinesWith); without the vocabulary of an extension, a verifier cannot check its critical actions.
  • Obligations of the PEP (Section 11.6): the agent is told nothing about audit mode, nor which resources of a request for several were not enforced (Section 4.4); implementations MUST show audit mode wherever they show mandates and SHOULD warn when a resource marked critical can be read under it; switching it is a mandate.updated. An implementation that does not offer audit mode MUST refuse to store a mandate with enforce: false.
  • Switching audit mode on (a mandate with enforce: false while no mandate with its id in audit mode is valid, active and in force, which includes a predecessor that is revoked, expired or not yet valid, a mandate from outside and a restore from a backup) MUST be confirmed explicitly by a human of the household before it takes effect, also for a signed successor. The confirmation is bound to the digest of the version that is adopted. Every approver MUST be notified when the switch is proposed and when it takes effect (Sections 3.5, 4.6, 11.6). Switching it off needs no confirmation.
  • A confirmed ask is not executed if the evaluation before the execution yields any deny, enforced or not; it is recorded with the confirmed ask, approved and denied_by: mandate (Sections 9.1, 11.1 item 6).
  • Conformance cases c174–c192 (new mandate conformance/mandates/audit-mode.json), i144–i148, d43–d44, a111–a125.
  • Reference evaluator (Go): Result.NotEnforced, Mandate.Enforce and EntersAuditMode (whether adopting a mandate switches audit mode on, compared with the stored mandate only if it is valid, active and in force).

Permitted values and string parameters (Sections 3, 3.1, 3.2, 4, 4.5, 5, 6, 8, 9.1, 10, 12):

  • A parameter is an integer or a string. The vocabulary declares the type of every parameter (type: integer with a unit, or string, optionally with permitted values); strings follow the rules of an area: 1 to 64 printable ASCII characters without space, compared exactly and case-sensitive. There are still no fractions. Incompatible for extension vocabularies (before v0.1.0): they must declare the type of every parameter and list at most 32 permitted values; mandates stay valid.
  • New constraint one_of: a list of 1 to 32 distinct permitted values of the parameter's type, for integer and string parameters; not together with min or max, which exist only for integer parameters. A rule with one_of matches only if the request carries the parameter with one of the values. A constraint that does not fit the declared type, or a string value outside the permitted values of the vocabulary, makes the mandate invalid (Section 3.1 item 10). Every mandate that was valid stays valid: a constraint with min or max on a parameter of the vocabulary was on an integer before as well.
  • climate.set_mode gets the string parameter mode with the permitted values of the Matter Thermostat SystemMode: off, auto, cool, heat, emergency_heat, precooling, fan_only, dry, sleep. A household can now allow heating modes but never off. The vocabulary keeps v0: it changes before the formats are frozen with v0.1.0. other.set stays critical and without parameters.
  • A request is invalid (invalid_request) if a parameter is neither an integer nor such a string, if a parameter the action declares has another type ("50" for brightness, 4 for mode), if a string is not among the permitted values, or if an integer lies outside the range of the vocabulary (brightness, position and volume 0 to 100; temperature and temperature_f have none). Before, the range was informative, and brightness: 101 was evaluated like any other value. Constraints beyond the range stay valid but never match. Before, set_mode had no parameters, and a PEP could pass any value without effect.
  • In the test interface (Section 10), in AuthZEN (action.properties) and in the audit entry (request.parameters), a parameter value is a JSON integer or, new, a JSON string. Incompatible: a string is never read as a number; the harness of the reference code formerly accepted "50" as the integer 50. The audit type stays https://home-mandate.org/audit/v0: the change comes before the formats are frozen. The audit log a37 carries a boolean instead of the string "40" (a string is now a valid value) and is chained anew; its expected result is unchanged.
  • Numbers are read by their exact value: a fraction is never rounded to an integer, however small it is (50.00000000000000000001, 1e-400). The reference code formerly read request parameters through floating point and took such values for integers.
  • Conformance cases c152–c173 (new mandate conformance/mandates/one-of.json), i129–i143, d41–d42, a109–a110.
  • Profiles: Home Assistant and Matter map their climate modes to the values of mode; Home Assistant heat_cool and auto both map to auto (Section 12.4).
  • Reference evaluator (Go), before v0.1.0: Request.Parameters is a map[string]Value; Value is an integer or a string (IntegerValue, StringValue) and reads and writes itself as JSON; Limit.OneOf holds the permitted values of a constraint.

Sets of areas (Sections 1.1, 2, 3.4, 4, 6, 8, 9.1, 10, 12):

  • A resource has a set of 0 to 16 areas instead of at most one: its room and every location above it (for example garage, ground floor, home), or several rooms side by side. A rule still names exactly one area and matches if that area is in the set; the mandate format does not change, and every mandate that was valid stays valid. A request with more than 16 areas, the same area twice or an area that is no identifier is invalid (invalid_request). The duty to report affected mandates covers every area in the set.
  • Incompatible: in the request of the test interface, in the directory of the HTTP binding, in AuthZEN (resource.properties.areas) and in the audit entry (request.resource.areas), area (a string) became areas (an array). An audit entry with request.resource.area is invalid. The audit type stays https://home-mandate.org/audit/v0: the change comes before the formats are frozen with v0.1.0. The audit logs in conformance/audit-v0.json carry areas and are chained anew; every expected result is unchanged.
  • Conformance cases c140–c151 (new mandate conformance/mandates/areas.json) and a106–a108.
  • Test interface (Section 10): a request with a member the section does not define is malformed, so that a former area is not evaluated as if it were absent.

Temperatures in degrees Fahrenheit (Sections 4.5, 5):

  • New parameter temperature_f of climate.set_temperature in hundredths of a degree Fahrenheit, next to temperature in hundredths of a degree Celsius, so that households whose platform uses °F can constrain temperatures exactly. The PEP derives exactly one of the two, in the platform's unit, and never converts. Compatible for implementations that load the vocabulary: a constraint on temperature_f was invalid before. The vocabulary keeps v0: the parameter is added before the formats are frozen with v0.1.0; from then on Section 5.1 holds without exception. Conformance cases c133–c139, i128.
  • Section 4.5 says plainly that a value that cannot be expressed as an integer in the unit makes the PEP refuse the request; it never leaves the parameter out.
  • Section 11.5 (new): a PEP MAY tell an agent the limits of its own mandate (the constraints of the allow rules that match apart from them, in the agent's units), but never for a resource the agent may not read, of another mandate, of deny or ask rules, in a unit it never derives, or while rules on a former identifier still apply. The reference evaluator (Go) offers Mandate.Limits for it; it decides nothing.

Overview (Sections 1.1 and 2, informative, no change to any rule):

  • Section 1.1 (new) shows in a diagram how agent, implementation (PEP, PDP, audit log), smart-home platform and human work together, which of these interfaces the specification defines and which it leaves open. It names MCP as the agent interface of the reference implementation, as an example.
  • Section 2 defines PDP and PEP.

The only path to the platform (Sections 12.2, 12.3, 12.4, text only, no change to any rule or format):

  • Section 12.3: new assumption that the agent reaches the platform only through the PEP, holds no other credential for it and cannot create or change scripts, automations, rules or other code that executes on it. Section 12.2: new row for an agent with a path past the PEP.
  • Sections 12.2 and 12.4: automations and rules that react to a harmless-looking resource; such a resource is marked critical, and interfaces show the household which resources automations react to where the platform can tell (openHAB as the example).
  • Section 12.4: no tools that write platform configuration through the PEP (a request for configuration is deny); the PEP's own platform credential is as restricted as the platform allows and never given to the agent.

MCP elicitation (Section 11.1, informative, no change to any rule):

  • Note after the list of Section 11.1: URL mode elicitation (MCP 2025-11-25 and later) can be a confirmation path in the sense of item 4 when the PEP checks that the person who confirms is an approver and binds the page to exactly one request; form mode elicitation cannot, because the answer passes through the agent's client. In both cases a PEP still needs a path to the approvers.

Confirmations for several resources (Sections 4.4, 11.1):

  • A confirmation for a request that the PEP resolves into several resources covers exactly the resolved set; the human is shown every resource of it (Section 11.1 item 2).
  • Before the execution the PEP resolves the request again and executes only if the set is unchanged; a resource added, removed or replaced in the meantime prevents the execution for the whole set, also when the mandate is unchanged and every resource still evaluates to ask or allow (Section 11.1 item 6). New RECOMMENDED code resources_changed (failed). No change to any format.
  • The PEP executes a request for several resources on exactly the resources it evaluated and names each of them to the platform, never the area, group or list it resolved them from, so that the platform cannot add a resource that was not evaluated (Section 4.4).

Versions (Section 14, rewritten):

  • The v0 formats (mandate, audit entry, audit checkpoint) are frozen from v0.1.0, the first release without a pre-release suffix. After that, every incompatible change requires a new type with the next major version; a document that was valid stays valid and is evaluated the same way. Until v0.1.0, incompatible changes remain possible and are listed here.
  • Defined what is incompatible: a valid document becomes invalid, or a valid document is evaluated differently (result, succession, digest, verification of an audit log).
  • Rule for mixed data: implementations read the older major and write the newer one; a verifier accepts an audit log whose earlier entries carry an older major; mandates move to a newer format only by succession and are never rewritten.
  • The vocabulary (Section 5.1) and the $ids of the conformance schemas are versioned independently of the formats.

incompatible

  • type of mandates, audit entries and checkpoints and the schema $id moved from https://mandate-spec.org/… to https://home-mandate.org/…, e.g. https://home-mandate.org/mandate/v0. Mandates with the old identifier are invalid (conformance case i127). Data model and evaluation are otherwise unchanged.
  • Because the type is part of every digest and signed payload, all digests, audit chains, checkpoint signatures and signed mandates in conformance/ are new. Cases that are invalid on purpose keep their defect.

added

  • Audit log: event template.changed with the member template records changes to templates, the stored mandate drafts from which an implementation creates mandates (Section 2): stored, removed, hidden and shown again, with the name and digests but never the content. Event approver.changed with the member approver records humans added to or removed from the implementation's list of approvers (Section 11.1). Both have an actor of kind user or system; template.name and approver.id are displayed text, and a previous_digest equal to digest makes an entry invalid (Sections 9.1, 9.2). Compatible for logs: every entry that was valid remains valid. A verifier that does not know the events rejects a log that contains them (Section 14, unknown members). Conformance cases a58–a76 in conformance/audit-v0.json.
  • Audit log: events mandate.removed and agent.removed record that a revoked mandate or a revoked agent was removed from the implementation's lists, with the existing members mandate (id and the digest of the last version) and agent; their actor is of kind user or system. Event agent.reconnected with the member agent records that a human gave new credentials to an existing agent that is not revoked, for example after an emergency stop; its actor is of kind user (Sections 9.1, 9.2, 11.3). Only revoked mandates and agents are removed; removal deletes no audit entries and keeps the protection against rollback (Section 3.5). Compatible for logs: every entry that was valid remains valid. A verifier that does not know the events rejects a log that contains them (Section 14, unknown members). Conformance cases a77–a88 in conformance/audit-v0.json.
  • Approval requests that end without an answer before their timeout: new outcome cancelled with the member approval.cause (withdrawn by the agent, revoked, emergency_stop, interrupted by the end of the process), only with result.status: denied and the denied_by matching the cause; nobody answered, so no by (Sections 9.1, 11.1 items 8 and 9). A request that a revocation or an emergency stop ends is recorded with this outcome instead of without an approval object. After a restart no waiting request is executed, reopened or answered; an execution whose effect is unknown is not repeated (failed, RECOMMENDED error: outcome_unknown). New RECOMMENDED codes already_in_state and state_changed for a confirmed action that is not executed because the state shown to the human no longer holds (Section 11.1 item 10). Compatible for logs: every entry that was valid remains valid. A verifier that does not know the outcome rejects a log that contains it (Section 14, unknown members). Conformance cases a89–a105 in conformance/audit-v0.json.

Actions the resource cannot carry out (Section 11.1 item 11): an implementation that knows that a resource cannot carry out the action SHOULD NOT ask anyone for it; the entry is failed with the new RECOMMENDED code not_supported and without an approval. Which actions the resource can carry out is told only to an agent that may read it (Section 13, new item on features). No change to the evaluation, to the vocabulary or to the format of mandates and logs. The profile for Home Assistant names where the features of an entity come from.

Conformance cases (no change to any rule or format):

  • Areas: c193–c199 (an area of 64 characters, the edge characters ! and ~, U+007F, non-ASCII, an empty area, Home next to home, a set in another order); two rules added to conformance/mandates/areas.json.
  • Parameters: c200–c209, c212, c213 (2^53 and 2^53 − 1, the spelling 5e1, the values of mode that one_of does not list, a constraint beyond the range of the vocabulary), d45–d48 and i149 (one_of with 32 values, a string value of 64 characters, min/max beyond the range, one_of for mode in a rule without category).
  • Audit mode: c210, c211 (new mandate conformance/mandates/audit-mode-critical.json: a deny rule on a resource marked critical), s20 (selection; the selection cases accept enforced as the evaluation cases do), v13, v14 (succession switching audit mode on and off), a127–a129.
  • Selection with a set of areas: s21. Audit log: a126 (not_supported, Section 11.1 item 11), a130–a133 (16 and 0 areas, a fraction and an empty string as parameter).

What request records (Section 9.1, text only, no change to any rule or format):

  • request records the input to the evaluation as the PEP determined it, also when the evaluation found it invalid (invalid_request); identifiers in it are limited in length and number, not in their characters, as the schema has always had it.
  • Input that the log cannot record (a longer identifier, more than 16 areas or areas that repeat, more than 16 parameters, a parameter value that is neither an integer nor a string of the form of Section 4.5) is not a request: the PEP rejects it at its interface before any evaluation. Conformance cases a134 (valid, an identifier with a space) and a135 (invalid, an entity_id of 256 characters).

Audit mode and ask (Sections 4, 4.1, 4.4, 4.6, 6, 8, 9.1, 10, 11.1, 11.6, 12, 13; issue #59):

  • In audit mode, a deny rule that wins over a matching ask rule no longer takes the confirmation away. The deny stays the decision and is not enforced, but the result requires a confirmation: it carries confirm: true and the approval settings of the ask (Section 4.1), and the PEP executes it only after a confirmation, as an ask (Sections 4.6, 11.1, 11.6). Before, such a request was executed without asking anyone, while the same request without the deny rule needed a confirmation. A deny without a matching ask rule is executed without a confirmation as before; critical actions are enforced as before.
  • New member confirm (only true, only with enforced: false and reason: rule) of the result of the evaluation, of the AuthZEN response context (Section 6, REQUIRED there together with approval_timeout and approvers), of the evaluate and select responses of the test interface and of the conformance cases (Sections 8, 10), and of evaluation in the audit log (Section 9.1). A PEP that executes a deny with enforced: false MUST know confirm; one that does not know enforced still enforces every deny. Implementations that already offer audit mode MUST add confirm, or they execute such a request without asking anyone.
  • Audit entry: an approval object appears with evaluation.decision: ask or with evaluation.confirm: true. With confirm: true, executed requires approval.outcome: approved, and the result can be anything an ask can have; without it, a deny with enforced: false is still only executed or failed. Every entry that was valid stays valid.
  • Before the execution (Section 11.1 item 6), a confirmed request with confirm: true is executed only if the evaluation then gives allow, ask or again a deny with confirm: true, so that a time window that has closed prevails as for an ask; a confirmed ask is still not executed after any deny. In the audit log, denied_by: mandate with confirm: true requires an approved or a revoked request. The human who confirms SHOULD be shown that the mandate in audit mode would deny the request, and by which rule (Section 11.1 item 2); the agent is not told.
  • Conformance cases c214–c223 (new mandate conformance/mandates/audit-mode-ask.json), s22 (the selection cases accept approval_timeout and confirm), a136 (valid: every outcome of an approval request with confirm: true) and a137–a145 (invalid).
  • Reference evaluator (Go): Result.Confirm; Result.Approval is also set with it.

Recording before executing, and the agent's values in the log (Sections 9, 9.1, 9.2, 11.1, 12.2, 12.4, 13; issues #86, #90):

  • A PEP stores what it will record before it dispatches a command, and does not execute when it cannot: new value audit of result.denied_by, for a request that was not executed because the log could not be written. Before, nothing said that recording comes first; a full disk or a crash between the command and the entry could execute an allow without a trace, although Section 9 promises a log that shows which agent did what. denied_by: audit appears only where no other denial came first: authentication, the emergency stop and the rate limit precede it; an answer other than approved is still denied by the approval, an enforced deny by the mandate; an unenforced deny (audit mode) can now end so as well. outcome_unknown after a restart now covers every action, not only confirmed ones. Section 9 says what durable means and what becomes of denials the PEP could not record yet. Compatible for writers of logs; a verifier of an earlier pre-release rejects an entry with the new value (before v0.1.0).
  • The values of request come from the agent and from the directory and may contain any character; whatever renders them for a human MUST treat them as untrusted text and show every character outside printable ASCII, and every backslash, as a JSON string shows them, never interpret them. Exports carry them unchanged. Text only: the schema already permitted the characters and Section 9.1 said so, but not what follows for interfaces, verifier reports and readers of exports. Conformance cases record such values. The reference verifier prints no value of an entry.
  • Conformance cases a203–a212.

Request parameters and vocabulary v0.1 (Sections 3.1, 4, 4.5, 5, 5.1, 9.1, 9.2, 10.2, 12.4, 14; issues #78, #84):

  • A request carries exactly the parameters its action declares: a parameter the action does not declare makes it invalid (c120, c160 and the unit tests change), as do more than 16 parameters, a name outside ^[a-z][a-z0-9_]{0,63}$, a missing required parameter, and none or more than one of the alternatives the action requires one of. The vocabulary file gains required per parameter and required_one_of per action, the table in Section 5 a column. Section 12.4 makes forwarding only what was evaluated a MUST. Incompatible for the class evaluator (before v0.1.0): c117–c120, c122, c138, c139, c158 and c160 are invalid_request now; c10, c12, c28–c30, c47–c49 carry the parameter their action requires.
  • Vocabulary v0.1: alarm.arm takes the required mode (home, away, night, vacation, custom_bypass); climate.set_temperature takes a range setpoint, temperature_low with temperature_high or the pair in degrees Fahrenheit, as one of its alternatives; mode, position and volume are required, brightness stays optional until the vocabulary names what else light.set sets (#29). Section 14 now counts a new parameter that is not required as compatible and a newly required one as incompatible.
  • Sections 9.1 and 9.2 name the parameter names among the input the log cannot record, and say that such input is not a request and gets no entry; the invalid_request results exist so that an evaluator fails closed. Section 3.1 item 10 keeps constraints off deny and ask rules and says why that still holds with required parameters.
  • No mandate changes validity: constraints on the new parameters become possible, nothing becomes invalid. decision entries recorded under vocabulary v0 keep their result; the version they name decides (Section 9.2).
  • Conformance cases c224–c242, new mandate conformance/mandates/parameters.json.

Acceptance and selection (Sections 3, 3.4, 3.5, 4.1, 4.3, 6, 7.1, 8, 8.1, 9.1, 9.2, 10.2, 11.3, 11.6, 12.2; issues #76, #79, #80, #81):

  • Selection (#76): an agent whose only mandates are revoked is denied with revoked, not no_mandate; the result names the revoked mandate if it is exactly one valid one. An invalid stored document is a candidate for the agent and principal it names, or for every agent and principal if these cannot be read. Section 4.3 says how a stored document becomes invalid and that the household is shown such documents. The class pdp runs the evaluation cases whose mandate is not revoked; its revoked is tested by the selection cases. Incompatible for the classes selection and pdp (before v0.1.0): s07 changes.
  • Succession (#79): the stored mandate of the succession rule includes one that is remembered after revocation, removal or expiry; the highest version is updated when a mandate takes effect, and a later successor replaces a waiting proposal.
  • Accepted is not in effect (#80): a successor that does not merely narrow the stored mandate (defined in Section 3.5), one that follows a revoked mandate, and a signed mandate with approvers the implementation does not know take effect only after a human confirmed them, as audit mode did. The succession operation of the process binding answers confirm: true for such a successor; cases carry stored_revoked and confirm. The reference code offers NeedsConfirmation and Narrows.
  • Signed mandates (#81): audience, an optional member of the mandate with the form of agent.client_id, required in a signed mandate together with expires; a verifier accepts a signed mandate only for its own audience, never fetches keys from a location a mandate names, and withdraws the effect of mandates accepted by a key it stops trusting. Incompatible for the class signatures (before v0.1.0): the signed cases are regenerated from a mandate with audience and expires; a mandate that uses audience is invalid for an implementation of an earlier pre-release (Section 14, unknown members).
  • Conformance cases s07 (changed), s24–s28, v01 and v09 (the offered mandate keeps the rate limit), v13 (confirm), v15–v23, g01–g21 (regenerated), g22, g23, d51, i156, i157.

AuthZEN mapping completed, optional mandate members, signed checkpoint time, further audit events, EdDSA canonicality (Sections 3, 3.1, 4.1, 4.3, 6, 7.1, 9.1, 9.2, 9.5, 10.2, 10.3, 11.4, 12.2; issues #77, #82, #83, #93):

  • AuthZEN (#77): resource.properties.critical and context.timezone carry the critical marking and the household time zone to a PDP that does not hold the directory; the response context carries mandate_id next to mandate_digest; a subject whose type is not agent, whose id is missing or whose principal does not match the pattern is invalid_request before any mandate is selected, also in the select operation of the process binding. The test tool compares mandate_id. Incompatible for the classes selection and pdp (before v0.1.0): such a subject was no_mandate.
  • Mandate (#82): optional name (1 to 64 code points) and per rule note (1 to 200), both displayed text, not part of the evaluation. A mandate that uses them is invalid for an implementation of an earlier pre-release (Section 14, unknown members).
  • Checkpoint (#82): the signed payload gains at, the recorded_at of the checkpoint entry. Incompatible for logs (before v0.1.0): checkpoints signed before do not verify.
  • Audit log (#83): events issuer.changed (member issuer: trusted, untrusted, key_added, key_removed, with kid for the keys) and config.changed (member config: timezone, clock_check, checkpoint_key, with value and previous_value). Compatible for logs: only entries that were invalid become valid; a verifier that does not know the events rejects a log that contains them. The reference verifier checks that previous_value differs from value.
  • Signatures (#93): for EdDSA, S < L and canonical encodings of R and of the public key; alg must match the key type. The text states what the reference already did; the reference now also rejects an Ed25519 public key that is not canonically encoded.
  • Conformance cases d50, i152–i155, s23, g19–g21, a194–a202; a39–a44, a46–a49 and a147 re-signed with the time in the payload; the selection cases that select no mandate now say selected: null, so that the HTTP binding checks that mandate_id is absent.

Proposals name the version they would replace (Sections 9.1, 9.2, 11.6; issue #53):

  • mandate.previous_digest is allowed with mandate.proposed and mandate.discarded: the digest of the version the implementation held when it recorded the proposal, present whenever there was one; a mandate.discarded repeats it. A proposal named only the proposed version, so a reader had to search the log for the version in force at the time.
  • Section 11.6 item 4 now requires (MUST) an implementation that offers audit mode to record proposals and discards, as the table in Section 9.2 already did, and says how the confirmation is linked to the proposal: the mandate.updated (or mandate.created, if no version was stored) carries the same digest and previous_digest; no member of its own.
  • Compatible for logs: only entries that were invalid become valid.
  • Conformance cases a190–a193; a157 reworded.

added audit events (Sections 3.5, 4.4, 7.1, 9.1, 9.2, 11.4, 11.6, 13; issues #53, #73):

  • log.exported, with optional exported (from_seq, to_seq): Section 13 asked for exports to be recorded, but no event existed for it.
  • directory.change gains added, removed, category_changed (previous_category, category) and areas_changed (previous_areas, areas). A resource that enters the scope of a rule, a category that changes the vocabulary that applies and a set of areas that changes which rules match were not recordable.
  • mandate.proposed and mandate.discarded (with mandate.cause): a mandate that would switch audit mode on and waits for a confirmation, and a proposal that does not take effect. Only the moment it took effect was recorded (#53). Added to audit/v0 now rather than to a later audit version: after the freeze, every new event needs a new type.
  • mandate.rejected (with mandate.cause, and mandate.id and digest as far as the document could be read): a mandate from outside that was not accepted. The protection against rollback and forged signatures left no trace in the log.
  • request.group, optional: connects the decision entries of one request for several resources.
  • Compatible: only entries that were invalid become valid; a verifier that does not know an event or a member rejects a log that contains it (Section 14, unknown members). The reference verifier checks from_seq ≤ to_seq < seq, and that category and areas differ from their former values.
  • Conformance cases a170–a189.

The vocabulary is bound to the formats (Sections 5, 5.1, 9.1, 9.2, 12.4, 14; issue #71):

  • The vocabulary https://home-mandate.org/vocabulary/v0 belongs to the v0 formats, and the freeze covers it: from v0.1.0, a new version under this id only adds (a new category, a new action, a new permitted value, a wider range). Removing or renaming a category or an action, changing a critical marking or narrowing a parameter needs a new id with a new major of the formats. Before, Section 14 said only that the vocabulary is versioned independently and never changes under the same id and version; nothing said what a new version may change, although a removed action would have made valid mandates invalid and a removed marking would have run an action without its confirmation. No change to any document: vocabulary/v0.json is unchanged and its version stays v0.
  • An implementation evaluates and verifies with the newest vocabulary version it knows. Until v0.1.0, the vocabulary can still change incompatibly, like the formats.
  • Audit entry: new optional member vocabulary (id, version) of a decision entry with evaluation, the vocabulary the evaluation used. For a category of Section 5 the id is that of v0, for an extension category that of the extension; the reference verifier checks it, and with the vocabularies of extensions it checks their id too (an extension vocabulary without id is now rejected). Absent with unknown_resource and unknown_category. Compatible: only entries that were invalid become valid; a verifier that does not know the member rejects a log that contains it (Section 14, unknown members). Implementations SHOULD record it, so that a decision stays reproducible after a vocabulary update.
  • Text only: "*" includes the actions a later version adds (Section 12.4); extensions SHOULD follow the same rule (Section 5.1).
  • Conformance cases a161–a169.

Schemas and formats in detail (Sections 3.1, 3.3, 3.4, 5.1, 9.1):

  • Audit entry: emergency_stop.released has an actor of kind user; mandate.created and mandate.updated have one of kind user or system. The text already said that only a human releases the emergency stop and that the human chooses the mandate; the schema permitted agent. Incompatible for logs (before v0.1.0). Agents may still revoke their own mandate or themselves and activate the emergency stop.
  • Audit entry: mandate.previous_digest appears only with mandate.updated (later also with mandate.proposed and mandate.discarded, see "Proposals name the version they would replace" above) and differs from digest, as for templates. Incompatible for logs (before v0.1.0).
  • Audit entry: evaluation.approval_timeout is required for ask and with confirm, and absent otherwise. Section 9.2 already required the entry to match the evaluation; the schema did not check it. Incompatible for logs (before v0.1.0).
  • Identifiers of humans have up to 128 characters everywhere: approvers in a mandate, approval.by and approver.id in the audit entry had 64, while created_by and actor.id had 128. Compatible: only documents that were invalid become valid.
  • An extension category in a mandate has at most 32 characters before and after the colon, as in a vocabulary file. Incompatible for mandates (before v0.1.0): a rule for a longer category could never match.
  • Text only: the size limit of a mandate counts the document as received, before canonicalization, as the reference code always did; the forms of agent.client_id and issuer name the scheme length and the query the schema permits, and that percent-encoded dots are not dot segments; approval.by with timeout names an approver whose answer came too late.
  • Conformance cases a153–a160, i150, i151, d49.

Verification of audit logs (Sections 8.1, 9.4, 9.5):

  • broken_at is determined condition by condition: a schema violation anywhere in the log comes before a wrong start or a broken chain, even if the chain breaks at an earlier entry. Before, the text also called it "the first entry in file order", which allowed two readings; the reference verifier has always reported the schema violation.
  • broken_at is 0 for an entry without a seq that can be read as an integer, and anchored is 0 for a log without a verified checkpoint, as the conformance cases already expected.
  • An entry is at most 65 536 bytes, as the reference verifier already required; a line of white space only is an entry that violates the schema like an empty line.
  • New rule: the up_to_seq of a log.truncated entry is smaller than the entry's own seq. Incompatible for logs (before v0.1.0): an entry that names itself or a later entry as deleted made the log invalid nowhere before; no implementation writes one.
  • All checkpoints of a log carry the same log_id also when no keys are given; the class audit covers this check, as the reference verifier already did.
  • Conformance cases a146–a152 in conformance/audit-v0.json.

Conformance report (Section 10.4, no change to any rule or format; issue #65):

  • implementation.version is the release of the implementation, not the version of the formats. Before, the reference harness reported v0; it now reports its release.
  • Over the HTTP binding the test tool takes implementation from the new options -name and -version; before, it stayed empty, because the HTTP binding has no capabilities.

History as mandate-spec

In the specification

incompatible mandates that were valid before can become invalid, and an invalid mandate denies every request. Implementations SHOULD check their stored mandates before they update:

  • agent.client_id (new Section 3.3): https URLs need a lower-case host and a path and must not contain userinfo, a fragment, white space or non-ASCII characters; no dot segments. Newly permitted: other URIs such as did:…, spiffe://…, urn:…. Identifiers of the form <namespace>:<id> remain valid.
  • Timestamps: at most 9 fractional digits; the year 0000 and the offset -00:00 are invalid (Section 3.1 item 0).
  • entity_id and area are opaque identifiers (new Section 3.4): printable ASCII without space, up to 255 and 64 characters, compared exactly. Every identifier that was valid before remains valid. The evaluation no longer rejects a request because of the form of an identifier; the PEP MUST resolve the requested resource in its directory (Section 4).
  • A resource without category is deny with the new reason code unknown_resource instead of invalid_request (Section 4.1).
  • A rule without category may only use actions that some category of the vocabulary has (Section 3.1 item 4); before, such actions were not checked.
  • scene.activate is critical (Section 5): an allow rule without allow_critical now yields ask.
  • allow_critical together with "*" is invalid (Section 3.1 item 9).
  • "No mandate for the agent and principal" is deny with the new reason code no_mandate instead of invalid_mandate; several mandates at the same time are ambiguous_mandate (Sections 4.1, 4.3 and 6).
  • Audit log: denied_by only with denied, no error with executed; evaluation.reason must belong to evaluation.decision; log.truncated only by a user or the system (Section 9.1).
  • Displayed text (Section 3.1 item 8): fixed code point list data/forbidden-codepoints-v0.json instead of the Unicode categories of the runtime; additionally forbidden are private-use and invisible (default ignorable) characters and characters, blank characters, noncharacters, every white space other than U+0020, a space at the start or end and a combining mark at the start; newly permitted are ZWNJ and ZWJ between other characters. The rule now also applies to actor.id, agent.display_name and approval.by in the audit log.

clarified each with new conformance cases:

  • Patterns apply to the entire string and use only a portable subset of regular expressions (Section 3).
  • approval.timeout: hours are permitted (PT1H); at most 5 digits per component (Section 3.1 item 7).
  • Numbers are read by their value: digest of a mandate (Section 3.2) and seq in the audit log (Section 9.1).
  • Time zones: links of the time zone database such as US/Eastern are permitted (Section 4.2).
  • Exchange format of the audit log: line feed as the only separator, no byte order mark, no empty lines (Section 9.4). conformance/audit-v0.json has the new field jsonl for it.

added

  • Conformance classes (Section 8.1): evaluator, selection, signatures, audit, audit-anchored, pdp.
  • Test interface (Section 10) with a process binding (JSON lines over standard input and output, no network) and an HTTP binding for PDPs; test tool cmd/mandate-conformance with a machine-readable report and cmd/mandate-harness as the harness of the reference code.
  • Sections 12 to 14: security considerations with the attackers and assumptions, privacy considerations, versions and compatibility. More requirements are stated with the key words of BCP 14.
  • Mandates can carry issuer and version; succession protects against rollback (Section 3.5, conformance/succession-v0.json).
  • Signed mandates: compact JWS over the canonical form with EdDSA or ES256, with exactly one accepted spelling; Section 7 is normative now (conformance/signed-v0.json).
  • Checkpoints anchor the audit log: event log.checkpoint with a log_id and a detached JWS over the position and digest of the log (Section 9.5); verification with keys reports up to which seq the log is anchored.
  • Section 11, obligations of the PEP: approval (binding, single use, independence from the agent, re-evaluation, limits), rate limit (at most N requests in every period of 3600 seconds, every request counts), revocation and emergency stop, clock.
  • AuthZEN: approvers in the response context for ask; trust between PEP and PDP (Section 6).
  • Audit log: result.count for combined rate limit denials; verification reports the number of entries; Section 9.4 states what the hash chain proves and what not.
  • Critical resources: the resource directory can mark a resource as critical; then every action except read is critical (Section 4, step 5). New optional input critical of the evaluation, in the conformance cases and in request.resource of the audit log.
  • Parameters and constraints (Section 4.5, Section 3.1 item 10): an allow rule can limit integer parameters of an action (temperature, position, volume, brightness); the vocabulary names the parameters and their units. New optional input parameters of the evaluation, in the conformance cases, in action.properties of the AuthZEN request and in request.parameters of the audit log.
  • Selection of the mandate (Section 4.3) with conformance/selection-v0.json; requests for several resources (Section 4.4).
  • vocabulary/v0.json: the vocabulary of Section 5 as a normative file, with schema/vocabulary-v0.schema.json; the same format for extensions (Section 5.1).
  • profiles/: informative mappings of platforms to the vocabulary. The Home Assistant and Matter columns left the table in Section 5.
  • Conformance cases with identifiers in the style of several platforms (conformance/mandates/identifiers.json).
  • Reference code: displaytext.Check, evaluator.Approval.Duration, evaluator.SelectAndEvaluate, evaluator.Resource.Critical, evaluator.Request.Parameters, package ratelimit (a limiter that meets the bound of Section 11.2), audit.VerifyJSONLines reads the log as a stream, package jws, evaluator.Sign, evaluator.ParseSigned, evaluator.CheckSuccessor, audit.SignCheckpoint, audit.VerifyAnchored.
  • conformance/schema/: JSON Schemas of the conformance files; conformance/manifest.json: all machine-readable files with SHA-256 and number of cases (Section 8).
  • LICENSE (Apache 2.0), LICENSE-docs (CC BY 4.0), SECURITY.md, CONTRIBUTING.md.
  • Audit log: optional approval.via, the channel an answer came through (Section 9.1), so that a log shows whether a person confirmed on a phone or in a user interface. Conformance cases a11–a13 in conformance/audit-v0.json.
  • Reference evaluator: IsCritical exposes the critical actions of the vocabulary (Section 5); anything outside the vocabulary counts as critical.
  • Audit log: event directory.changed with the member directory records changes to the resource directory that affect the evaluation: the critical marking, renamed resources and how a human resolved a rename (Sections 9.1, 9.2 and 11.4). Required for implementations that let humans change the critical marking or track renamed resources. Compatible for logs: every entry that was valid remains valid. A verifier that does not know the event rejects a log that contains it (Section 14, unknown members). Conformance cases a50–a57 in conformance/audit-v0.json.
  • Examples in English: examples/*.json use English display names, device identifiers and areas, and so do the conformance cases on them. Their digests change: d01–d03, d06 and d08 in conformance/digest-v0.json. The audit vectors keep their mandate digest, which is only data there.

incompatible

  • type and schema $id moved to the neutral domain: https://mandate-spec.org/mandate/v0. Name of the specification: mandate-spec.
  • New validity rules for mandates (Section 3.1): format: date-time is validated, no leap seconds, I-JSON, unique rule IDs, time_window with distinct start and end, actions matching the category, at most 256 KiB, expires after valid_from, approval timeout 10 s to 1 h, no control or format characters in displayed texts.
  • agent.client_id: https URL or <namespace>:<id> instead of the product-specific prefix hm-client: (existing hm-client: IDs remain valid).
  • Request: the entity ID is required and must match the pattern, as must the area; the status of the mandate is required (active or revoked).
  • The schemas are normative.

clarified (Section 4), each with new conformance cases:

  • Pre-check: unknown category, action outside the vocabulary, invalid time specification or invalid mandate → deny.
  • Validity period valid_from ≤ t < expires; revocation as an input to the evaluation.
  • Local time via the household time zone; time window start inclusive, end exclusive; weekday of the point in time; behavior during daylight saving time transitions.
  • rule_id and approval settings in the result (Section 4.1).
  • limits are not part of the evaluation.
  • Extensions without a known vocabulary → deny.
  • Origin of inputs: category, area, time, time zone and status come from the PEP, never from the agent; comparison exact per character and case-sensitive.
  • Time zones: only UTC or IANA identifiers of the form Area/Location; seconds are truncated.
  • AuthZEN mapping of the fields (Section 6); the Home Assistant and Matter columns in the vocabulary are informative.
  • Audit log: required fields per event, executed only after a permitting decision, broken_at also on schema violations (Section 9).

added

  • Digest of a mandate (Section 3.2), reason codes (Section 4.1), both required in the AuthZEN response context (Section 6).
  • Audit log with a fixed format and hash chain (Section 9, schema/audit-v0.schema.json).
  • Schema: time fields additionally with pattern.

Conformance cases: new fields reason (required), timezone, revoked, rule_id, approval_timeout; new files conformance/invalid-v0.json, conformance/digest-v0.json, conformance/audit-v0.json; test mandates under conformance/mandates/.