SharedOS API v0.1.0-alpha.3


SharedOS API / @aicoo/sharedos-precedent

@aicoo/sharedos-precedent

Admission rules for an auto-decision proposed from an owner's prior answers.

npm install @aicoo/sharedos-precedent@next

A precedent is a resolved escalation: the CapabilityRequest that was asked, what the owner answered, and — when they approved — the width they approved. Nothing new is stored to have one, and nothing is stored here: the rows stay host-side behind PrecedentLookup, because a precedent is a record of what one owner answered and SharedOS stores nothing.

The host proposes; this package admits. A matcher may be an exact key match, Jaccard over tool names, embeddings, or a model trained on the host's own history — this does not rank, score, learn, or improve. It answers one question about a finished proposal, may this be decided without a human?, against ADR 0022's four rules, and the proposal carries no similarity score, confidence, or match type: a field the kernel is handed and must ignore is a field a host will eventually expect it to honour.

import {
  admitAutoDecision,
  autoDecisionAuditEvent,
} from "@aicoo/sharedos-precedent";

const admission = await admitAutoDecision(
  {
    request, // the escalation now in front of the control plane
    citedRequestIds: matcher.cite(request), // however the host found them
    proposed: { allowed: true, capabilities: [oneNarrowCapability] },
    marker: { matcher: "jaccard-v3" },
  },
  precedentLookup,
);

if (admission.admitted) {
  await auditSink.record(autoDecisionAuditEvent(context, admission.decision));
  if (admission.decision.allowed) {
    await grants.issue({
      /* ... */
      capabilities: admission.decision.capabilities,
      constraints: admission.decision.constraints,
      metadata: admission.decision.metadata,
    });
  }
}

What it decides, and what it never decides

A precedent decides whether to ask, never whether to permit. Nothing here is an AuthorizationDecision, nothing returned is assignable to one, and no port accepts one back as authority. An admitted allow describes a grant for the host's store to issue; the next turn loads it through GrantSource like any other, and the turn that escalated stays ended. There is no consent queue, no resumption, and no pending grant state.

That a denial cannot become an allow is structural rather than a rule: RefusedPrecedent has no capabilities, so there is no width on a refusal for an allow to read, and citing one in a proposed allow is inadmissible.

The four rules

rulewhat it holds
R1Fuzzy evidence may only narrow. similar + deny is admissible; similar + allow comes back narrowed and bounded by what this request asked for; full width needs an exact key match.
R2Never wider than the precedents cited, checked with capabilityIsWithin — the predicate the delegation chain already uses — once per precedent. Disjoint precedents make every proposal refused.
R3The tightest envelope: minimum expiry, latest start, minimum bounded use, purpose intersection, delegationDepth: 0. It bounds one decision, never a class.
R4Every auto-decision is marked. A proposal that declares no matcher is inadmissible, and the marker reaches the grant's metadata and an escalation.auto_decided audit event.

R4 is what makes a bad matcher a rollback instead of an incident: an operator selects everything one matcher produced — readAutoDecided off the grant, or the audit stream — and revokes it in one action, without telling it apart from what people decided by hand.

Exactness is derived, not declared

A host never asserts that a match was exact; the assertion is exactly what R1 gates, and a rule enforced against a self-report is a rule enforced against honest hosts only. PrecedentKey is the structured key — namespace, owner, requester, purpose, and effective capability — and precedentKeyDigest is its only fingerprint. Equality of the digest is an exact match; anything else is fuzzy evidence, including a citation that mixes one identical precedent with a similar one.

The key is deliberately time-invariant, as CapabilityRequest.id is since ADR 0019. The id is still not usable for this: it hashes the constraints the requester asked for, and R3 takes the envelope from the precedents rather than from the ask, so an ask for a shorter expiry must not be a different question.

It is also keyed on effective capability rather than on a grant set. A host that holds authority down outside its grants — a tool map, an allow-list, an ADR 0020 HostCeiling — has an effective authority narrower than its grant set, and keying on the wider one would teach the system an authority nobody granted, most of all in the deployments that were being careful.

See ADR 0022.

SharedOS is currently an 0.x prerelease.

Classes

InMemoryPrecedentLookup

Defined in: precedent/src/lookup.ts:71

A process-local lookup over a fixed set of rows, for tests and single-process hosts. Durable hosts inject their own store.

Implements

Constructors

Constructor

new InMemoryPrecedentLookup(precedents?): InMemoryPrecedentLookup

Defined in: precedent/src/lookup.ts:74

Parameters
ParameterTypeDefault value
precedentsreadonly Precedent[][]
Returns

InMemoryPrecedentLookup

Methods

load()

load(namespaceId, requestIds): Promise<readonly Precedent[]>

Defined in: precedent/src/lookup.ts:90

Parameters
ParameterType
namespaceIdstring
requestIdsreadonly string[]
Returns

Promise<readonly Precedent[]>

Implementation of

PrecedentLookup.load

record()

record(precedent): void

Defined in: precedent/src/lookup.ts:80

Parameters
ParameterType
precedentPrecedent
Returns

void

Interfaces

AdmittedAllow

Defined in: precedent/src/admission.ts:121

An auto-decision that allows, at a width the kernel bounded.

allowed: true and nothing else: this is not a third AuthorizationDecision value and is deliberately not assignable to one. It describes a grant for the host's store to issue, which the next turn loads through GrantSource like any other. Nothing here is authority, no port accepts one back as authority, and no turn is resumed by it.

Extends

  • AdmittedCommon

Properties

PropertyModifierTypeDescriptionInherited fromDefined in
<a id="property-allowed"></a> allowedreadonlytrue--precedent/src/admission.ts:122
<a id="property-capabilities"></a> capabilitiesreadonlyreadonly object[]--precedent/src/admission.ts:132
<a id="property-citedrequestids"></a> citedRequestIdsreadonlyreadonly string[]-AdmittedCommon.citedRequestIdsprecedent/src/admission.ts:94
<a id="property-constraints"></a> constraintsreadonlyobjectR3's tightest envelope across every precedent cited.-precedent/src/admission.ts:134
constraints.delegationDepth?publicnumber--contracts/dist/capability.d.ts:228
constraints.expiresAt?publicstring--contracts/dist/capability.d.ts:226
constraints.maxUses?publicnumber--contracts/dist/capability.d.ts:227
constraints.notBefore?publicstring--contracts/dist/capability.d.ts:225
constraints.purposes?publicstring[]--contracts/dist/capability.d.ts:224
<a id="property-match"></a> matchreadonlyPrecedentMatch-AdmittedCommon.matchprecedent/src/admission.ts:95
<a id="property-metadata"></a> metadatareadonlyJsonObjectR4, ready to put on the grant and on the audit event.AdmittedCommon.metadataprecedent/src/admission.ts:97
<a id="property-narrowed"></a> narrowedreadonlybooleanTrue when fuzzy evidence carried it, which is ADR 0022's allow_narrowed. Not a decision value. It serialises as an ordinary allow; what differs is that the capability below was additionally bounded by what this request actually asked for, because resemblance may not authorize more than the question in front of us needs.-precedent/src/admission.ts:131

AdmittedDeny

Defined in: precedent/src/admission.ts:108

An auto-decision that refuses.

It issues nothing, so its marker reaches only the audit event. A refusal still has to be markable: an operator revoking everything one matcher produced has to be able to find the requests it silently closed as well as the grants it opened.

Extends

  • AdmittedCommon

Properties

PropertyModifierTypeDescriptionInherited fromDefined in
<a id="property-allowed-1"></a> allowedreadonlyfalse--precedent/src/admission.ts:109
<a id="property-citedrequestids-1"></a> citedRequestIdsreadonlyreadonly string[]-AdmittedCommon.citedRequestIdsprecedent/src/admission.ts:94
<a id="property-match-1"></a> matchreadonlyPrecedentMatch-AdmittedCommon.matchprecedent/src/admission.ts:95
<a id="property-metadata-1"></a> metadatareadonlyJsonObjectR4, ready to put on the grant and on the audit event.AdmittedCommon.metadataprecedent/src/admission.ts:97

ApprovedPrecedent

Defined in: precedent/src/lookup.ts:14

A resolved escalation a human approved, and the width they approved.

capabilities is what the owner actually said yes to, which is not always what was asked: an owner who approves a narrower thing than the request named produces a precedent narrower than its own key. Both are recorded, because the key answers "was this the same question" and the capabilities answer "how much did they allow".

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-capabilities-1"></a> capabilitiesreadonlyreadonly object[]-precedent/src/lookup.ts:19
<a id="property-constraints-1"></a> constraintsreadonlyobject-precedent/src/lookup.ts:20
constraints.delegationDepth?publicnumber-contracts/dist/capability.d.ts:228
constraints.expiresAt?publicstring-contracts/dist/capability.d.ts:226
constraints.maxUses?publicnumber-contracts/dist/capability.d.ts:227
constraints.notBefore?publicstring-contracts/dist/capability.d.ts:225
constraints.purposes?publicstring[]-contracts/dist/capability.d.ts:224
<a id="property-decidedat"></a> decidedAtreadonlystring-precedent/src/lookup.ts:21
<a id="property-key"></a> keyreadonlyPrecedentKey-precedent/src/lookup.ts:18
<a id="property-outcome"></a> outcomereadonly"approved"-precedent/src/lookup.ts:15
<a id="property-requestid"></a> requestIdreadonlystringThe CapabilityRequest.id of the escalation this resolved.precedent/src/lookup.ts:17

AutoDecidedMarker

Defined in: precedent/src/admission.ts:36

What a host declares about the matcher behind a proposal.

matcher is the class handle R4 exists for: a product will improve its matcher, some improvement will be wrong, and the difference between that being an incident and being a rollback is whether an operator can select everything one matcher produced and revoke it in one action. Name the matcher and its version, not the request -- "the thing to revoke" is a generation of matcher, never a single decision.

Extended by

Properties

PropertyModifierTypeDefined in
<a id="property-matcher"></a> matcherreadonlystringprecedent/src/admission.ts:37

AutoDecidedRecord

Defined in: precedent/src/admission.ts:44

The marker as it reaches the grant and the audit event, with the citation the kernel derived rather than the host asserted.

Extends

Properties

PropertyModifierTypeDescriptionInherited fromDefined in
<a id="property-citedrequestids-2"></a> citedRequestIdsreadonlyreadonly string[]--precedent/src/admission.ts:45
<a id="property-match-2"></a> matchreadonlyPrecedentMatchWhether the cited evidence was the identical question. R1's axis.-precedent/src/admission.ts:47
<a id="property-matcher-1"></a> matcherreadonlystring-AutoDecidedMarker.matcherprecedent/src/admission.ts:37

AutoDecisionProposal

Defined in: precedent/src/admission.ts:71

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-citedrequestids-3"></a> citedRequestIdsreadonlyreadonly string[]The precedents the host's matcher chose, by CapabilityRequest.id.precedent/src/admission.ts:75
<a id="property-marker"></a> markerreadonlyAutoDecidedMarker-precedent/src/admission.ts:77
<a id="property-proposed"></a> proposedreadonlyProposedAutoDecision-precedent/src/admission.ts:76
<a id="property-request"></a> requestreadonlyobjectThe escalation now in front of the control plane.precedent/src/admission.ts:73
request.capabilitiespublicobject[]-contracts/dist/capability.d.ts:511
request.constraints?publicobject-contracts/dist/capability.d.ts:534
request.constraints.delegationDepth?publicnumber-contracts/dist/capability.d.ts:539
request.constraints.expiresAt?publicstring-contracts/dist/capability.d.ts:537
request.constraints.maxUses?publicnumber-contracts/dist/capability.d.ts:538
request.constraints.notBefore?publicstring-contracts/dist/capability.d.ts:536
request.constraints.purposes?publicstring[]-contracts/dist/capability.d.ts:535
request.idpublicstring-contracts/dist/capability.d.ts:496
request.metadata?publicJsonObject-contracts/dist/capability.d.ts:541
request.namespaceIdpublicstring-contracts/dist/capability.d.ts:497
request.ownerpublic{ kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }-contracts/dist/capability.d.ts:483
request.purposepublicstring-contracts/dist/capability.d.ts:532
request.requestedAtpublicstring-contracts/dist/capability.d.ts:533
request.requesterpublic{ kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }-contracts/dist/capability.d.ts:498

PrecedentKey

Defined in: precedent/src/key.ts:24

The shape a precedent is filed under, as a structure rather than a string.

The c2c plane this replaces string-encoded a structured key into two fields meant for something else: relationshipCluster carried c2c:<principalId> and queryFingerprint carried a JSON tuple, where the escalation plane wrote a computed cluster and a SHA-256 of normalized intent. One table, two grammars, and nothing that could tell them apart. Declaring the key makes that inexpressible -- there is no field here a second encoding could hide in, and precedentKeyDigest is the only fingerprint.

The five dimensions are the ones that decide whether this is the same question. requestedAt is deliberately not among them: see precedentKeyDigest.

Properties

PropertyModifierTypeDescriptionDefined in
<a id="property-capabilities-2"></a> capabilitiesreadonlyreadonly object[]The effective capability asked for: what the owner was actually answering about, not the grant set that answer produced. A host that holds authority down outside its grant set -- a tool map, an allow-list, an ADR 0020 ceiling -- has an effective authority narrower than its grants. Keying on the grants would record that a human approved authority the host was quietly withholding, and the next proposal would cite that record to justify the wider thing. See ADR 0022.precedent/src/key.ts:41
<a id="property-namespaceid"></a> namespaceIdreadonlystring-precedent/src/key.ts:25
<a id="property-owner"></a> ownerreadonly{ kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }Whose answer this is. A precedent is one owner's record, never a pool.precedent/src/key.ts:27
<a id="property-purpose"></a> purposereadonlystring-precedent/src/key.ts:30
<a id="property-requester"></a> requesterreadonly{ kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }Who was asking. Only an allow reads this; see admitAutoDecision.precedent/src/key.ts:29

PrecedentLookup

Defined in: precedent/src/lookup.ts:63

The trusted lookup for the precedents a proposal cites.

SharedOS stores nothing. A precedent is a record of what one owner answered, and that record belongs to the host that recorded the escalation -- so the rows stay host-side and reach admission through this port, by id, at the moment they are judged. Nothing here writes, expires, or garbage-collects a row; a host that already records resolved escalations already has the material and needs no new table.

The port is asked for the ids a proposal named, and nothing else. It is not a matcher: a search interface here would put ranking inside the kernel, which is the one thing ADR 0022 keeps out. Whichever rows a host's matcher chose, it cites them by id and they are re-read from the store, so a proposal can never overstate what a precedent said.

An implementation must resolve only within namespaceId, and must throw rather than answer with a partial or stale set -- an admission built on a row the store could not vouch for is exactly the widening nobody authorized. admitAutoDecision fails closed on a throw and on any answer that is not precisely the cited ids.

Methods

load()

load(namespaceId, requestIds): Promise<readonly Precedent[]>

Defined in: precedent/src/lookup.ts:64

Parameters
ParameterType
namespaceIdstring
requestIdsreadonly string[]
Returns

Promise<readonly Precedent[]>


RefusedPrecedent

Defined in: precedent/src/lookup.ts:32

A resolved escalation a human refused.

It has no capabilities, and that absence is load-bearing rather than tidy: it is why a proposed allow cannot read a width off a refusal. A denial needs no width to be worth citing, so nothing is missing here -- there was never a width to record. See admitAutoDecision.

Properties

PropertyModifierTypeDefined in
<a id="property-decidedat-1"></a> decidedAtreadonlystringprecedent/src/lookup.ts:36
<a id="property-key-1"></a> keyreadonlyPrecedentKeyprecedent/src/lookup.ts:35
<a id="property-outcome-1"></a> outcomereadonly"refused"precedent/src/lookup.ts:33
<a id="property-requestid-1"></a> requestIdreadonlystringprecedent/src/lookup.ts:34

Type Aliases

AdmittedAutoDecision

AdmittedAutoDecision = AdmittedDeny | AdmittedAllow

Defined in: precedent/src/admission.ts:137


Precedent

Precedent = ApprovedPrecedent | RefusedPrecedent

Defined in: precedent/src/lookup.ts:39


PrecedentAdmission

PrecedentAdmission = { admitted: true; decision: AdmittedAutoDecision; } | { admitted: false; reason: PrecedentInadmissibleReason; }

Defined in: precedent/src/admission.ts:139


PrecedentInadmissibleReason

PrecedentInadmissibleReason = "no_precedent_cited" | "auto_decision_unmarked" | "empty_proposed_capability" | "precedent_unavailable" | "precedent_not_this_owner" | "precedent_not_this_requester" | "allow_cites_refusal" | "wider_than_precedent" | "wider_than_request" | "envelope_unsatisfiable"

Defined in: precedent/src/admission.ts:81

Why a proposal may not be decided without a human.


PrecedentMatch

PrecedentMatch = "exact" | "fuzzy"

Defined in: precedent/src/admission.ts:57

Whether the cited precedents are the same question or merely a similar one.

Derived here, never declared: a host that could assert "this was exact" would be self-reporting the very thing R1 gates, and a rule enforced against a self-report is a rule enforced against honest hosts only.


ProposedAutoDecision

ProposedAutoDecision = { allowed: false; } | { allowed: true; capabilities: readonly Capability[]; }

Defined in: precedent/src/admission.ts:67

What the host wants to happen, carrying no score, confidence, or match type.

A field the kernel is handed and must ignore is a field a host will eventually expect it to honour -- ADR 0009 rejected a context carrying grants the kernel ignored for exactly that reason -- so the proposal has nowhere to put one.

Variables

AUTO_DECIDED_METADATA_KEY

const AUTO_DECIDED_METADATA_KEY: "autoDecided" = "autoDecided"

Defined in: precedent/src/admission.ts:24

The reserved metadata key an auto-decided grant carries.

Reserved and validated rather than left to opaque host metadata, and that is consistent with ADR 0008 refusing to put the delegation parent there: 0008's objection was to authority resting on an unvalidated field. This one grants nothing and removes nothing. It is a handle, and the only thing that has to be true of a handle is that it is present -- which only a validated key makes checkable.


PRECEDENT_KEY_VERSION

const PRECEDENT_KEY_VERSION: "1" = "1"

Defined in: precedent/src/key.ts:98

The key shape this digest is over. Hashed, so versions cannot collide.

Functions

admitAutoDecision()

admitAutoDecision(proposal, lookup): Promise<PrecedentAdmission>>

Defined in: precedent/src/admission.ts:186

Decide whether one finished proposal may be decided without a human.

The host proposes however it likes -- exact key match, Jaccard over tool names, embeddings, a model trained on its own history. This does not rank, score, learn, or improve; it answers one question about a proposal that is already made, against R1 through R4 of ADR 0022. That separation is the whole point: a product's matching can get better every quarter without the kernel ever having to trust a similarity score, and what a security review reads is four rules rather than a model.

R1 -- fuzzy evidence may only narrow. A deny is admissible on either evidence: refusing something resembling what this owner has refused takes nothing away that was not already absent, and its worst case is an escalation the owner never sees, which is today's case for every request. An allow is the only outcome that creates authority, so only an exact key match may authorize at the full width a human approved; a fuzzy allow is additionally bounded by the capabilities this request asked for and comes back narrowed.

R2 -- never wider than the precedents cited. Checked with capabilityIsWithin, the same predicate the delegation chain uses, applied once per cited precedent rather than against a constructed intersection -- the same guarantee without an intersection algebra the contract would then have to define and test. Cited precedents that are disjoint make every proposal inadmissible, which is the correct answer.

R3 -- the tightest envelope. Minimum expiry, latest start, minimum bounded use, the intersection of allowed purposes, and delegationDepth: 0. A machine-made grant that can be delegated is one whose blast radius is decided by somebody else. This bounds one auto-decision, never a class: n auto-decisions citing a k-use precedent carry n*k uses between them, exactly as ADR 0008 already found for delegation. What bounds the class is R4.

R4 -- every auto-decision is marked. A proposal that declares no marker is inadmissible, and the marker returned carries the derived citation. Honouring it at issue is the host's, as ADR 0011 already divides resolution.

Refusing a proposal is not an event. The escalation it concerns is already recorded and already waiting for a human; manufacturing a second one would double-count in every denominator. Inadmissible means the auto-decision does not happen and the request stays where it was.

Parameters

ParameterType
proposalAutoDecisionProposal
lookupPrecedentLookup

Returns

Promise<PrecedentAdmission>


autoDecisionAuditEvent()

autoDecisionAuditEvent(context, decision): AuditEvent

Defined in: precedent/src/audit.ts:28

The escalation.auto_decided event for one admitted auto-decision.

R4's other half. An operator asking "what did the machine decide on our behalf, and what did it cite" reads it from the audit stream, and that question is the one that decides whether a matcher stays turned on.

It takes an admitted decision and nothing else, so an inadmissible proposal has no way to become an event. That is deliberate: the escalation it concerns is already recorded and already waiting for a human, and a second record of the same unanswered request would double-count in every denominator ADR 0011 was careful about.

The outcome is allowed or denied, never escalated. An escalation is a decision SharedOS declined to make; this is one that was made, by a matcher the host installed, on a precedent a human set. Recording it as escalated would count a machine answer as a request for help.

context is the control plane's, not a turn's: the auto-decision happens where the human's would have, between turns, against an escalation that is already terminal.

Parameters

ParameterType
context{ actor: { kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }; authority: { kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }; enabledToolNamespaces: string[]; namespaceId: string; now: string; owner: { kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }; purpose: string; traceId: string; }
context.actor{ kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }
context.authority{ kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }
context.enabledToolNamespacesstring[]
context.namespaceIdstring
context.nowstring
context.owner{ kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }
context.purposestring
context.traceIdstring
decisionAdmittedAutoDecision

Returns

AuditEvent


precedentKey()

precedentKey(request): PrecedentKey

Defined in: precedent/src/key.ts:52

The key of the request in front of us, or of the one a precedent recorded.

Reads only the dimensions above and drops id, requestedAt, constraints and metadata. Dropping the requested constraints cannot widen anything: R3 takes the envelope from the precedents, so what a requester asked to be bounded by never reaches the issued grant.

Parameters

ParameterType
request{ capabilities: object[]; constraints?: { delegationDepth?: number; expiresAt?: string; maxUses?: number; notBefore?: string; purposes?: string[]; }; id: string; metadata?: JsonObject; namespaceId: string; owner: { kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }; purpose: string; requestedAt: string; requester: { kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }; }
request.capabilitiesobject[]
request.constraints?{ delegationDepth?: number; expiresAt?: string; maxUses?: number; notBefore?: string; purposes?: string[]; }
request.constraints.delegationDepth?number
request.constraints.expiresAt?string
request.constraints.maxUses?number
request.constraints.notBefore?string
request.constraints.purposes?string[]
request.idstring
request.metadata?JsonObject
request.namespaceIdstring
request.owner{ kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }
request.purposestring
request.requestedAtstring
request.requester{ kind: "human"; userId: string; } | { agentId: string; kind: "agent"; } | { conversationId: string; kind: "group"; } | { kind: "service"; serviceId: string; }

Returns

PrecedentKey


precedentKeyDigest()

precedentKeyDigest(key): Promise<string>>

Defined in: precedent/src/key.ts:86

A deterministic fingerprint of one key, and the only thing exactness is derived from.

ADR 0022 proposed re-deriving CapabilityRequest.id and comparing. The id is time-invariant since ADR 0019 -- mintCapabilityRequest keeps requestedAt out of the hashed material, so one ask keeps one id across turns -- but it is still not this key: the id hashes the constraints the requester asked to be bounded by, and R3 takes the envelope from the precedents, not from the ask. Keying on the id would make a request that asked for a shorter expiry a different question from one that did not. The key reads only what decides whether it is the same question, which is what "the same question" has to mean.

Normalisation is what makes two hosts agree. Object keys are already ordered by canonicalJson; on top of that an unowned resource is resolved against the key's owner (they denote the same resource once the owner is known), actions are deduplicated and sorted, and the capability list is deduplicated and sorted by its own canonical form. Order of declaration is not part of the question being asked.

version is hashed with the rest so a later change to what a key contains cannot collide with a digest computed under this one.

Parameters

ParameterType
keyPrecedentKey

Returns

Promise<string>


readAutoDecided()

readAutoDecided(metadata): AutoDecidedRecord | undefined

Defined in: precedent/src/admission.ts:411

The marker on a grant, or undefined when it carries none.

The reading half of R4: an operator selecting everything one matcher produced asks this of each grant's metadata rather than parsing a convention. A grant with no marker was decided by a person, which is the distinction the whole rule exists to keep drawable.

Parameters

ParameterType
metadataJsonObject | undefined

Returns

AutoDecidedRecord | undefined