~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
138 lines · 9.0 KB Raw History

policy: policy records, attached policies, tangled-vouch

  • a market policy ALWAYS runs, both sides, both lanes (scope + full), every decision. no skip paths
  • the requester publishes the workflow it runs as a record; the RFP strongRefs it; the bidder runs it next to its own
  • caching lives inside the engine run, never across parties, never a skip
  • index: README.md. sequence steps: S1 (vouches), S3 (record + RFP), S4 (bidder runs), S5 (requester runs)

Records And Fields

NSID / fieldtyperkeywritten byread byGostep
computer.socialweb.temp.policy.ghaliterecord (no lexicon file)TID (createRecord without rkey); one per contract, kept in state.Policyrequesterbidder (via the RFP strongRef), requester (re-check)internal/market/requester/policy.go BuildPolicyRecord, internal/market/requester/runner.go, internal/market/participant/policyrecord.goS3
market.rfp policies[]array of com.atproto.repo.strongRef-requesterbidderinternal/atproto/market/records.go BuildRFP, CreateRFPS3
computer.socialweb.infra#contract policy, policyArgsstrings-ownerrequester (via the ComputeContract)internal/market/requester/runner.goS2
sh.tangled.graph.vouchrecord (outside)subject DIDoperators, usersboth policies (trust snapshot)internal/policyengine/trust/trust.goS1
computer.socialweb.temp.policy.typescriptrecord (no lexicon file)-nobodydeniedinternal/policyengine/registry/registry.go-
com.publicdomainrelay.temp.market.policies.denoWorkerrecord (no lexicon file)-nobodydeniedinternal/policyengine/registry/registry.go-
computer.socialweb.temp.policy.wasmtest fixture only--denied as an unknown typeinternal/policyengine/controller/records.go-
  • no policy field on market.bid, market.offering or compute.*. the bidder's own Bid object keeps spec.policy {attached, verdict} in kcp only

computer.socialweb.temp.policy.ghalite

fieldvalue
$typecomputer.socialweb.temp.policy.ghalite
namethe workflow name, e.g. requester-tangled-vouch
workflowthe full YAML text of the bundled workflow (or its name when there is no text)
argsmap[string]string, from policyArgs
requesterthe requester's market DID
perspectiverequester
policies[{name, args?}]
createdAtdatetime
  • PolicyRecordWorkflow reads workflow as a YAML string or an object and denies any other $type (internal/market/participant/policyrecord.go)
  • typescript and denoWorker executors are registered as deny ("the policy engine is gha-lite only")
  • OAuth: requester and bidder sets carry create (+ update on requester) for ghalite and typescript; none for denoWorker (outside.md)

Infra Contract Fields

  • policy: the workflow name. resolution: cmp.Or(contract.Spec.Policy, cfg.PolicyWorkflow, DefaultPolicyWorkflow)
  • PolicyWorkflowName normalizes: "" -> requester-tangled-vouch; only-me -> requester-only-me; mutuals -> requester-mutuals; tangled-vouch -> requester-tangled-vouch
  • the name must be a bundled workflow, else the contract fails before the RFP
  • policyArgs: a JSON object, parsed to map[string]string (internal/market/participant/policy.go ParsePolicyArgs)

RFP Policy StrongRefs

sequenceDiagram
  autonumber
  participant R as requester
  participant RP as requester repo
  participant B as bidder
  participant E as policy engine (bidder)

  R->>R: policy name from Infra contract / ComputeContract
  R->>RP: S3 createRecord policy.ghalite {workflow text, args}
  R->>RP: getRecord it back: own repo, cid, name, body == bundled workflow
  R->>RP: S3 market.rfp {policies: [strongRef]}
  RP-->>B: market.rfp
  B->>E: own workflow (bidder-tangled-vouch), scope lane (DIDs only)
  B->>RP: getRecord each policies[] ref
  B->>E: own workflow + each attached workflow, full lane
  E-->>B: allow only if all allow
  B->>B: S4 bid or no bid
  • requester write path: runner.go step creatingRfp: BuildPolicyRecord -> CreateRecord -> attachedPolicy -> rfpOptions.Policies -> market.CreateRFP
  • requester re-check attachedPolicy (internal/market/requester/policy.go): the record is in the requester's own repo, the cid matches, the name matches the contract, the body equals the bundled workflow byte for byte. rfpPolicyRef requires exactly one policy
  • bidder: EvaluatePolicies (internal/market/bidder/policy.go) runs its own workflow (default bidder-tangled-vouch), then each RFP ref via RepoStore.Resolve (local getRecord or a foreign resolver), PolicyRecordWorkflow, runs.Request{Workflow, Source: uri}. first deny wins
  • the bidder runs the attached workflow with perspective=bidder (internal/market/bidder/bids.go)
  • a restarted runner reuses state.Policy; it does not write a second record

Lanes And Modes

laneinputwhere
scopeDIDs only, RFP nil, mode: scopebidder bids.go, internal/policyengine/executor/ghalite.go Scope
fullself, subject, counterparty, root requester DIDs, rfp, bid, accept, args, mode: fullbidder bids.go fullInput, adapters.go; ghalite.go Execute
  • executor: GhaLite (internal/policyengine/executor/ghalite.go), default timeout 30 s
  • run path: runs.Client.Run creates a PolicyWorkflowRun and waits (internal/policyengine/runs/client.go)
  • missing engine, evaluator or a non-boolean allow = deny

Caching (inside the run)

cachekeyTTL
trust snapshot seedtrust/<counterpartyDid>per run
recordrec/<uri>/<cid> (internal/policyengine/cache/cache.go)-
verdictpolicy/<name>/<rfp>|<bid>|<accept>[:argsHash]30 s default
scope verdict memointernal/policyengine/controller/scopememo.go30 s, plus a negative TTL
vouch, operator lookupstrust.Resolver ttlcache5 min, errors 5 s

Bundled Workflows

  • embedded: internal/policyengine/bundled-actions/embed.go (workflows/, tangy/), looked up by Workflow(name) and Lookup(uses)
  • every workflow is a workflow_dispatch wrapper with inputs self-did, rfp, bid, accept, policy-args, mode, counterparty-did, perspective, subject-did, root-requester-did, cache-key; it calls tangy/policy-<name>@v1 and outputs allow, violations
workflowkinddecides
bidder-tangled-vouchtrust (bidder default)the requester or its operator is vouched by the bidder or its operator
requester-tangled-vouchtrust (requester default)the same, for the bidder, from the requester side
bidder-mutuals, requester-mutualstrustoperators follow each other on Bluesky
bidder-only-me, requester-only-metrustthe counterparty resolves to the own operator
opentrustno restriction. only when named, never prod
bid-payloadwork (requester)the bid payload NSID is in allowedPayloadNsids (default market.bids.free)
under-4-cpusworkthe VM asks at most maxCpus vCPUs
  • the action infers its perspective from the bidder- prefix unless the perspective input says otherwise
  • defaults: api/v1alpha1/types_computevm.go BidderDefaultPolicy, RequesterDefaultPolicy

Tangled-Vouch

  • default on both sides everywhere; open only if the user sets it
  • a vouch is sh.tangled.graph.vouch at rkey = the subject DID in the voucher's repo; it counts when it exists and kind != "denounce": outside.md
  • both directions are needed for a deal: the bidder side vouches for the requester side and the requester side for the bidder side
flowchart LR
  SELF["self"] --> T["trusted = self + operator(self)"]
  CP["counterparty"] --> OPC["operator(counterparty) via identity.md"]
  T --> Q{"one getRecord per pair: trusted vouches counterparty or operator(counterparty)?"}
  OPC --> Q
  Q -->|yes| ALLOW["allow"]
  Q -->|operator(counterparty) is trusted| ALLOW
  Q -->|no| DENY["deny"]
  • snapshot: trust.BuildSnapshotWith (internal/policyengine/trust/trust.go); depth 1, no transitive walk
  • operator resolution (claims, associations, mirror proofs): identity.md
  • who writes vouches: market networks declare operator vouches both ways (internal/app/marketnetwork/vouches.go, compose ApplyOperatorVouch); people write them from any client. the requester OAuth scope offers vouch write as an optional scope
  • requester discovery lists the vouches of its trusted DIDs (internal/market/requester/discovery.go vouchedBy, a listRecords that skips denounce and takes rkeys starting did:)

See Also