@byollm/protocol 0.1.0-alpha.45 → 0.1.0-alpha.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  > [!WARNING]
2
- > **Alpha (`0.1.0-alpha.45`) — under active development. Don't use this yet.**
2
+ > **Alpha (`0.1.0-alpha.47`) — under active development. Don't use this yet.**
3
3
  >
4
4
  > Install it deliberately: `npm install @byollm/protocol@alpha`.
5
5
  >
@@ -83,7 +83,7 @@
83
83
  > packages published and `@byollm/server` did not: a Sigstore
84
84
  > transparency-log 409 on its provenance attestation. The workflow's
85
85
  > "already published" guard correctly refuses to resume a partial publish,
86
- > so `0.1.0-alpha.45` is that release, whole.
86
+ > so `0.1.0-alpha.47` is that release, whole.
87
87
  >
88
88
  > If you run the Supabase adapter, `alpha.21` needs
89
89
  > `20260819010000_completed_by_lease_id.sql`: alpha.19 shipped §3.6's
package/dist/index.d.ts CHANGED
@@ -104,11 +104,23 @@ declare const BACKENDS: Readonly<{
104
104
  readonly mistral: BackendDescriptor;
105
105
  readonly "openai-http": BackendDescriptor;
106
106
  readonly "claude-cli": BackendDescriptor;
107
+ /**
108
+ * OpenAI's Codex CLI, on a ChatGPT plan — byollm_016 stage 3.
109
+ *
110
+ * `subscription`, so `SUBSCRIPTION_SELF_LOCK` pins it to its owner's own
111
+ * work whatever the config says. That is load-bearing here in a way it is
112
+ * not for `claude-cli`: Codex is an *agent*, and its default feature set
113
+ * includes a shell tool, browser control and computer use. The daemon
114
+ * disables every one of them, verified against the shipped binary rather
115
+ * than assumed — see `codex-cli.ts` — but the self-lock is the floor under
116
+ * that verification rather than a duplicate of it.
117
+ */
118
+ readonly "codex-cli": BackendDescriptor;
107
119
  }>;
108
120
  /** The id of a registered backend. */
109
121
  type BackendId = keyof typeof BACKENDS;
110
122
  /** All registered backend ids — the adversarial coverage check iterates this. */
111
- declare const BACKEND_IDS: readonly ("ollama" | "mlx" | "llamacpp" | "vllm" | "lmstudio" | "jan" | "localai" | "anthropic" | "openai" | "gemini" | "grok" | "groq" | "openrouter" | "together" | "deepseek" | "mistral" | "openai-http" | "claude-cli")[];
123
+ declare const BACKEND_IDS: readonly ("ollama" | "mlx" | "llamacpp" | "vllm" | "lmstudio" | "jan" | "localai" | "anthropic" | "openai" | "gemini" | "grok" | "groq" | "openrouter" | "together" | "deepseek" | "mistral" | "openai-http" | "claude-cli" | "codex-cli")[];
112
124
  declare const BackendIdSchema: z.ZodEnum<{
113
125
  ollama: "ollama";
114
126
  mlx: "mlx";
@@ -128,6 +140,7 @@ declare const BackendIdSchema: z.ZodEnum<{
128
140
  mistral: "mistral";
129
141
  "openai-http": "openai-http";
130
142
  "claude-cli": "claude-cli";
143
+ "codex-cli": "codex-cli";
131
144
  }>;
132
145
  /** Narrow an arbitrary string to a registered backend id. */
133
146
  declare function isBackendId(value: string): value is BackendId;
@@ -659,6 +672,58 @@ declare const JobOutcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
659
672
  outcome: z.ZodLiteral<"canceled">;
660
673
  }, z.core.$strict>], "outcome">;
661
674
  type JobOutcome = z.infer<typeof JobOutcome>;
675
+ /**
676
+ * Why a job can never run — byollm_016 Phase B.
677
+ *
678
+ * Every one of these is **terminal**, and that is the whole point of naming
679
+ * them. A job that cannot be matched used to sit queued until its deadline,
680
+ * which reads exactly like a job that is merely waiting for a device to come
681
+ * online — so an app could not tell "any moment now" from "never", and neither
682
+ * could the person watching a spinner. Silence must never read as pending.
683
+ *
684
+ * They are decided by whoever knows first: the site's own SDK where it can see
685
+ * the answer without asking, the router where matching happens, and the daemon
686
+ * again on arrival under the both-sides rule. All three reason from the same
687
+ * list rather than three private vocabularies.
688
+ */
689
+ declare const RefusalReason: z.ZodEnum<{
690
+ "select-unavailable": "select-unavailable";
691
+ "default-ambiguity": "default-ambiguity";
692
+ "default-unusable": "default-unusable";
693
+ }>;
694
+ type RefusalReason = z.infer<typeof RefusalReason>;
695
+ /**
696
+ * A terminal outcome nobody sealed — byollm_016 Phase B.
697
+ *
698
+ * Every other finished job carries an envelope encrypted by the device that
699
+ * ran it, which is what makes a result unforgeable. These have no device: the
700
+ * job was refused *before* anything could run it, so there is nobody to seal
701
+ * from and no content to seal.
702
+ *
703
+ * **What that costs, stated plainly.** This is the one terminal outcome a
704
+ * router can author. It is worth being exact about the power that grants,
705
+ * because "the relay can write this" sounds alarming until you compare it with
706
+ * what a relay could already do: drop the job, never offer it, and let it
707
+ * expire. A router-authored refusal is *denial of service by a shorter route*,
708
+ * which is a power the router has always had and which the trust model has
709
+ * always said it has. What it emphatically is **not** is forgery: this shape
710
+ * carries no envelope and no output, so it can never be mistaken for an answer
711
+ * a device produced. A relay still cannot fabricate a result, because that
712
+ * needs a signature it does not hold.
713
+ *
714
+ * So the rule this shape enforces by construction: a refusal may deny, and may
715
+ * never assert. Anything that claims work was *done* still comes sealed.
716
+ */
717
+ declare const JobRefused: z.ZodObject<{
718
+ outcome: z.ZodLiteral<"refused">;
719
+ reason: z.ZodEnum<{
720
+ "select-unavailable": "select-unavailable";
721
+ "default-ambiguity": "default-ambiguity";
722
+ "default-unusable": "default-unusable";
723
+ }>;
724
+ message: z.ZodString;
725
+ }, z.core.$strict>;
726
+ type JobRefused = z.infer<typeof JobRefused>;
662
727
  /**
663
728
  * The plaintext inside a result envelope.
664
729
  *
@@ -799,6 +864,7 @@ declare const JobStub: z.ZodObject<{
799
864
  team: "team";
800
865
  public: "public";
801
866
  }>;
867
+ service: z.ZodOptional<z.ZodString>;
802
868
  sizeClass: z.ZodEnum<{
803
869
  small: "small";
804
870
  medium: "medium";
@@ -823,6 +889,7 @@ declare const ClaimedStub: z.ZodObject<{
823
889
  team: "team";
824
890
  public: "public";
825
891
  }>;
892
+ service: z.ZodOptional<z.ZodString>;
826
893
  sizeClass: z.ZodEnum<{
827
894
  small: "small";
828
895
  medium: "medium";
@@ -1398,6 +1465,27 @@ declare const MUSTS: Readonly<{
1398
1465
  readonly PROVENANCE_NAMES_DEVICE: Must;
1399
1466
  readonly INGRESS_LOGGED_BEFORE_EXECUTION: Must;
1400
1467
  readonly NO_SHELL_INTERPOLATION: Must;
1468
+ /**
1469
+ * Amended for byollm_016 Phase B, and the amendment is deliberately narrow.
1470
+ *
1471
+ * A site may now name a **service** on the stub. The temptation is to read
1472
+ * that as a crack in this law, so the statement below says exactly where the
1473
+ * line is: a name selects from a menu the owner published, and resolves to a
1474
+ * model, backend, base URL and flags **only** through that owner's own
1475
+ * config. The site supplies a key; the owner supplies every value it maps
1476
+ * to. A name the owner does not advertise is refused rather than
1477
+ * substituted, because substitution is how "you may pick from my list" turns
1478
+ * into "you may ask for anything and get something".
1479
+ *
1480
+ * Two properties keep it from drifting into "sites demand models":
1481
+ *
1482
+ * 1. **Nothing the site sends is ever a value.** No model string, no URL,
1483
+ * no flag crosses the wire — only a key that means nothing off this
1484
+ * owner's machine.
1485
+ * 2. **It is a stub field, never a payload field.** The prompt cannot
1486
+ * reach it. That is unchanged and is the sentence the second clause
1487
+ * below still enforces verbatim.
1488
+ */
1401
1489
  readonly NO_PAYLOAD_ROUTING: Must;
1402
1490
  readonly STRIPPED_CHILD_ENV: Must;
1403
1491
  readonly HTTP_BASE_URL_SAFE: Must;
@@ -1526,6 +1614,7 @@ declare const Capability: z.ZodObject<{
1526
1614
  mistral: "mistral";
1527
1615
  "openai-http": "openai-http";
1528
1616
  "claude-cli": "claude-cli";
1617
+ "codex-cli": "codex-cli";
1529
1618
  }>;
1530
1619
  backendClass: z.ZodEnum<{
1531
1620
  http: "http";
@@ -1566,6 +1655,7 @@ declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
1566
1655
  mistral: "mistral";
1567
1656
  "openai-http": "openai-http";
1568
1657
  "claude-cli": "claude-cli";
1658
+ "codex-cli": "codex-cli";
1569
1659
  }>;
1570
1660
  backendClass: z.ZodEnum<{
1571
1661
  http: "http";
@@ -1658,6 +1748,7 @@ declare const PairStartRequest: z.ZodObject<{
1658
1748
  mistral: "mistral";
1659
1749
  "openai-http": "openai-http";
1660
1750
  "claude-cli": "claude-cli";
1751
+ "codex-cli": "codex-cli";
1661
1752
  }>;
1662
1753
  backendClass: z.ZodEnum<{
1663
1754
  http: "http";
@@ -1747,6 +1838,7 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
1747
1838
  mistral: "mistral";
1748
1839
  "openai-http": "openai-http";
1749
1840
  "claude-cli": "claude-cli";
1841
+ "codex-cli": "codex-cli";
1750
1842
  }>;
1751
1843
  backendClass: z.ZodEnum<{
1752
1844
  http: "http";
@@ -1794,6 +1886,7 @@ declare const ClaimRequest: z.ZodObject<{
1794
1886
  mistral: "mistral";
1795
1887
  "openai-http": "openai-http";
1796
1888
  "claude-cli": "claude-cli";
1889
+ "codex-cli": "codex-cli";
1797
1890
  }>;
1798
1891
  backendClass: z.ZodEnum<{
1799
1892
  http: "http";
@@ -1823,6 +1916,7 @@ declare const ClaimResponse: z.ZodObject<{
1823
1916
  team: "team";
1824
1917
  public: "public";
1825
1918
  }>;
1919
+ service: z.ZodOptional<z.ZodString>;
1826
1920
  sizeClass: z.ZodEnum<{
1827
1921
  small: "small";
1828
1922
  medium: "medium";
@@ -1870,6 +1964,7 @@ declare const HeartbeatRequest: z.ZodObject<{
1870
1964
  mistral: "mistral";
1871
1965
  "openai-http": "openai-http";
1872
1966
  "claude-cli": "claude-cli";
1967
+ "codex-cli": "codex-cli";
1873
1968
  }>;
1874
1969
  backendClass: z.ZodEnum<{
1875
1970
  http: "http";
@@ -1986,11 +2081,11 @@ declare const ReleaseRequest: z.ZodObject<{
1986
2081
  leaseId: z.ZodString;
1987
2082
  }, z.core.$strip>>;
1988
2083
  reason: z.ZodEnum<{
2084
+ refused: "refused";
1989
2085
  shutdown: "shutdown";
1990
2086
  pause: "pause";
1991
2087
  revoked: "revoked";
1992
2088
  "backend-down": "backend-down";
1993
- refused: "refused";
1994
2089
  }>;
1995
2090
  }, z.core.$strict>;
1996
2091
  type ReleaseRequest = z.infer<typeof ReleaseRequest>;
@@ -2065,4 +2160,4 @@ declare const FetchResponse: z.ZodObject<{
2065
2160
  }, z.core.$strict>;
2066
2161
  type FetchResponse = z.infer<typeof FetchResponse>;
2067
2162
 
2068
- export { AUDIENCES, Audience, BACKENDS, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, DeliveredResult, ENCRYPTION_KEY_CONTEXT, ENDPOINTS, ENVELOPE_MAX_AGE_MS, ERROR_STATUS, type Endpoint, type EnvelopeContext, EnvelopeDirection, type EnvelopeFailure, FetchRequest, FetchResponse, GeneratePayload, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, type MustVerification, type MustVerifiedBy, OFFER_SCOPES, OfferScope, type OpenResult, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, PublicIdentity, REFUSAL_MESSAGES, RETIREMENT_WINDOW_MS, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SizeClass, type SpendConsent, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, type VersionRefusal, WireError, WireErrorCode, WithheldKind, backendDescriptor, canTransition, canonicalRequest, checkProtocolVersion, cryptoReady, declaredVersion, effectiveOfferScope, fingerprint, generateKeys, isBackendId, isCloudTaggedModel, isJobKind, isLocalHost, isTerminal, keyId, kindsOf, matchAudience, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signRequest, signSiteRequest, signSuccession, signWith, sizeClassCeiling, sizeClassOf, successionStatement, verifyLink, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith, walkSuccession };
2163
+ export { AUDIENCES, Audience, BACKENDS, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, DeliveredResult, ENCRYPTION_KEY_CONTEXT, ENDPOINTS, ENVELOPE_MAX_AGE_MS, ERROR_STATUS, type Endpoint, type EnvelopeContext, EnvelopeDirection, type EnvelopeFailure, FetchRequest, FetchResponse, GeneratePayload, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobRefused, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, type MustVerification, type MustVerifiedBy, OFFER_SCOPES, OfferScope, type OpenResult, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, PublicIdentity, REFUSAL_MESSAGES, RETIREMENT_WINDOW_MS, RefusalReason, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SizeClass, type SpendConsent, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, type VersionRefusal, WireError, WireErrorCode, WithheldKind, backendDescriptor, canTransition, canonicalRequest, checkProtocolVersion, cryptoReady, declaredVersion, effectiveOfferScope, fingerprint, generateKeys, isBackendId, isCloudTaggedModel, isJobKind, isLocalHost, isTerminal, keyId, kindsOf, matchAudience, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signRequest, signSiteRequest, signSuccession, signWith, sizeClassCeiling, sizeClassOf, successionStatement, verifyLink, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith, walkSuccession };
package/dist/index.js CHANGED
@@ -163,6 +163,24 @@ var BACKENDS = Object.freeze({
163
163
  class: "process",
164
164
  cost: "subscription",
165
165
  adversarialCorpus: "process"
166
+ }),
167
+ /**
168
+ * OpenAI's Codex CLI, on a ChatGPT plan — byollm_016 stage 3.
169
+ *
170
+ * `subscription`, so `SUBSCRIPTION_SELF_LOCK` pins it to its owner's own
171
+ * work whatever the config says. That is load-bearing here in a way it is
172
+ * not for `claude-cli`: Codex is an *agent*, and its default feature set
173
+ * includes a shell tool, browser control and computer use. The daemon
174
+ * disables every one of them, verified against the shipped binary rather
175
+ * than assumed — see `codex-cli.ts` — but the self-lock is the floor under
176
+ * that verification rather than a duplicate of it.
177
+ */
178
+ "codex-cli": backend({
179
+ id: "codex-cli",
180
+ label: "Codex CLI (your ChatGPT plan)",
181
+ class: "process",
182
+ cost: "subscription",
183
+ adversarialCorpus: "process"
166
184
  })
167
185
  });
168
186
  var BACKEND_IDS = Object.freeze(Object.keys(BACKENDS));
@@ -466,6 +484,67 @@ var JobOutcome = z4.discriminatedUnion("outcome", [
466
484
  JobResultError,
467
485
  JobResultCanceled
468
486
  ]);
487
+ var RefusalReason = z4.enum([
488
+ /**
489
+ * A selection this requester cannot be served — byollm_016 Phase B.
490
+ *
491
+ * **One value for two causes, and the collapse is the security property.**
492
+ * A named service may be unknown to this owner, or known and not offered to
493
+ * this requester. Those are different facts and the requester may learn
494
+ * neither, because telling them apart turns refusal wording into an
495
+ * inventory oracle: probe names, sort the answers, and enumerate a device
496
+ * you were never offered. The finer cause lives owner-side, where the person
497
+ * reading it already owns the machine — see {@link SelectionFailure}.
498
+ *
499
+ * The first draft of this enum had both causes on the wire with a comment
500
+ * claiming they disclosed identically. They did not; the comment described a
501
+ * property the code lacked, which is the more dangerous half of that mistake.
502
+ */
503
+ "select-unavailable",
504
+ /**
505
+ * Two or more services answer this kind and the owner has named no default,
506
+ * so the kind is withheld. Nobody may pick on the owner's behalf — the wrong
507
+ * guess is the metered one.
508
+ *
509
+ * Not collapsed into the value above, and the reason is that a kind is not
510
+ * probeable. There are two kinds; a requester asking about one is not
511
+ * enumerating a namespace, and learns nothing they could not learn by
512
+ * looking at what the device advertises. It is also already what a roster
513
+ * member sees on the devices page — `awaitingDefault` carries exactly this,
514
+ * by kind, for exactly this reason.
515
+ */
516
+ "default-ambiguity",
517
+ /**
518
+ * A default exists and this requester can never use it — byollm_016's
519
+ * defaults-meet-audiences corner.
520
+ *
521
+ * The specimen: an owner's default for `llm.chat` is their Claude
522
+ * subscription, self-locked by `SUBSCRIPTION_SELF_LOCK`. A team member's
523
+ * unselected job resolves to it and can never be served by it. That must be
524
+ * a refusal on the spot, not a wait that expires an hour later looking like
525
+ * nobody was online.
526
+ *
527
+ * Bounded like the value above and probeable for the same reason it is not:
528
+ * the requester named nothing, so there is no name space to walk.
529
+ */
530
+ "default-unusable"
531
+ ]);
532
+ var JobRefused = z4.object({
533
+ outcome: z4.literal("refused"),
534
+ reason: RefusalReason,
535
+ /** Plain words for a human reading a log, never parsed. */
536
+ message: z4.string().min(1)
537
+ }).strict();
538
+ var REFUSAL_TEXT = Object.freeze({
539
+ "select-unavailable": "that service is not available to you on this device",
540
+ "default-ambiguity": "this device serves that kind from more than one service and its owner has not chosen which",
541
+ "default-unusable": "this device's default for that kind cannot run work for you"
542
+ });
543
+ var REFUSED_SELECTION = Object.freeze({
544
+ outcome: "refused",
545
+ reason: "select-unavailable",
546
+ message: REFUSAL_TEXT["select-unavailable"]
547
+ });
469
548
  var SealedOutcome = z4.object({ outcome: JobOutcome, ran: RunMetadata }).strict();
470
549
  var DeliveredResult = z4.object({
471
550
  jobId: z4.string().min(1),
@@ -553,6 +632,30 @@ var JobStub = z4.object({
553
632
  // The site keeps its own copy on `JobRecord` and still filters candidates
554
633
  // with it before offering. That is server-internal, where the party
555
634
  // holding the list authored it.
635
+ /**
636
+ * Which of the owner's services should answer — byollm_016 Phase B.
637
+ *
638
+ * **A selection from a menu, never a demand.** The owner advertises named
639
+ * services; a site may name one of them, and that is the entire power the
640
+ * field grants. It carries no model, no base URL, no flags — the daemon
641
+ * resolves the name against its own config and nothing else, so what
642
+ * actually runs is still decided exclusively by the person who owns the
643
+ * hardware. A name that is not on that owner's menu is refused
644
+ * (`select-unadvertised`), never silently substituted, because a
645
+ * substitution is how "select" would quietly become "whatever we had".
646
+ *
647
+ * Absent means "the owner's default for this kind", which is the only
648
+ * behaviour Phase A had.
649
+ *
650
+ * It travels because the router matches on it, under the rule the absent
651
+ * `audienceAllow` above establishes: *a class the router acts on may
652
+ * travel; membership never does.* This is a class.
653
+ *
654
+ * It is a **stub** field and never a payload field, which is the line
655
+ * `NO_PAYLOAD_ROUTING` draws: the prompt cannot reach it, so no amount of
656
+ * user text can influence what runs.
657
+ */
658
+ service: z4.string().min(1).optional(),
556
659
  sizeClass: SizeClass,
557
660
  /** Reserved for byollm_006. False until streaming exists. */
558
661
  streaming: z4.boolean(),
@@ -1221,12 +1324,33 @@ var MUSTS = Object.freeze({
1221
1324
  verifiedBy: "adversarial",
1222
1325
  source: "byollm_004 \xA72"
1223
1326
  }),
1327
+ /**
1328
+ * Amended for byollm_016 Phase B, and the amendment is deliberately narrow.
1329
+ *
1330
+ * A site may now name a **service** on the stub. The temptation is to read
1331
+ * that as a crack in this law, so the statement below says exactly where the
1332
+ * line is: a name selects from a menu the owner published, and resolves to a
1333
+ * model, backend, base URL and flags **only** through that owner's own
1334
+ * config. The site supplies a key; the owner supplies every value it maps
1335
+ * to. A name the owner does not advertise is refused rather than
1336
+ * substituted, because substitution is how "you may pick from my list" turns
1337
+ * into "you may ask for anything and get something".
1338
+ *
1339
+ * Two properties keep it from drifting into "sites demand models":
1340
+ *
1341
+ * 1. **Nothing the site sends is ever a value.** No model string, no URL,
1342
+ * no flag crosses the wire — only a key that means nothing off this
1343
+ * owner's machine.
1344
+ * 2. **It is a stub field, never a payload field.** The prompt cannot
1345
+ * reach it. That is unchanged and is the sentence the second clause
1346
+ * below still enforces verbatim.
1347
+ */
1224
1348
  NO_PAYLOAD_ROUTING: must({
1225
1349
  id: "NO_PAYLOAD_ROUTING",
1226
- statement: "Model, backend, base URL, and flags MUST come from owner config only; a payload MUST NOT influence any of them.",
1350
+ statement: "Model, backend, base URL, and flags MUST come from owner config only; a payload MUST NOT influence any of them. A stub MAY name a service the owner advertises, which selects among that owner's own config entries and MUST NOT introduce any value the owner did not write; an unadvertised name MUST be refused, never substituted.",
1227
1351
  enforcedBy: "daemon",
1228
1352
  verifiedBy: "adversarial",
1229
- source: "byollm_004 \xA72"
1353
+ source: "byollm_004 \xA72, amended byollm_016 \xA7Phase B"
1230
1354
  }),
1231
1355
  STRIPPED_CHILD_ENV: must({
1232
1356
  id: "STRIPPED_CHILD_ENV",
@@ -1967,6 +2091,7 @@ export {
1967
2091
  JobKind,
1968
2092
  JobOutcome,
1969
2093
  JobPayload,
2094
+ JobRefused,
1970
2095
  JobResultCanceled,
1971
2096
  JobResultError,
1972
2097
  JobResultOk,
@@ -1993,6 +2118,7 @@ export {
1993
2118
  PublicIdentity,
1994
2119
  REFUSAL_MESSAGES,
1995
2120
  RETIREMENT_WINDOW_MS,
2121
+ RefusalReason,
1996
2122
  ReleaseRequest,
1997
2123
  ReleaseResponse,
1998
2124
  RequestSignature,