primitive-admin 1.1.0-alpha.75 → 1.1.0-alpha.76
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/dist/bin/primitive.js +98 -10
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/functions.js +53 -18
- package/dist/src/commands/functions.js.map +1 -1
- package/dist/src/commands/prompts.js +54 -39
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/sync.d.ts +39 -0
- package/dist/src/commands/sync.js +163 -90
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/workflows.js +61 -30
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +16 -1
- package/dist/src/lib/api-client.js +16 -3
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/block-selector.d.ts +58 -0
- package/dist/src/lib/block-selector.js +92 -0
- package/dist/src/lib/block-selector.js.map +1 -0
- package/dist/src/lib/config-object-descriptor.js +1 -3
- package/dist/src/lib/config-object-descriptor.js.map +1 -1
- package/dist/src/lib/function-collect.d.ts +29 -7
- package/dist/src/lib/function-collect.js +135 -11
- package/dist/src/lib/function-collect.js.map +1 -1
- package/dist/src/lib/function-grants-preflight.d.ts +34 -88
- package/dist/src/lib/function-grants-preflight.js +32 -145
- package/dist/src/lib/function-grants-preflight.js.map +1 -1
- package/dist/src/lib/function-sync.d.ts +3 -14
- package/dist/src/lib/function-sync.js +10 -49
- package/dist/src/lib/function-sync.js.map +1 -1
- package/dist/src/lib/function-triggers.d.ts +6 -0
- package/dist/src/lib/function-triggers.js +63 -4
- package/dist/src/lib/function-triggers.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +188 -236
- package/dist/src/lib/generated-config-surfaces.js +355 -453
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/generated-sdk-types.d.ts +1 -1
- package/dist/src/lib/generated-sdk-types.js +1 -1
- package/dist/src/lib/generated-sdk-types.js.map +1 -1
- package/dist/src/lib/toml-database-config.js +19 -2
- package/dist/src/lib/toml-database-config.js.map +1 -1
- package/package.json +1 -1
|
@@ -579,39 +579,47 @@ export declare const RETIRED_CONFIG_KEYS: Record<string, Record<string, RetiredC
|
|
|
579
579
|
/** The retired-key entry for `key` under `prefix`, or null when it is simply unknown. */
|
|
580
580
|
export declare function retiredConfigKey(prefix: string, key: string): RetiredConfigKey | null;
|
|
581
581
|
/**
|
|
582
|
-
* The
|
|
583
|
-
* `server-functions` phase
|
|
582
|
+
* The capability grammar for server functions — #3182 phase 1, rewritten by
|
|
583
|
+
* #3279 (project `server-functions` phase 3).
|
|
584
584
|
*
|
|
585
|
-
* A function declares
|
|
585
|
+
* A function declares in its own TOML what it may CONFIGURE:
|
|
586
586
|
*
|
|
587
|
-
* capabilities = ["
|
|
588
|
-
*
|
|
587
|
+
* capabilities = ["integration:stripe", "secret:STRIPE_KEY", "databases:delete"]
|
|
588
|
+
*
|
|
589
|
+
* ── Why the grammar is this small ────────────────────────────────────────
|
|
590
|
+
*
|
|
591
|
+
* The intent's decision (2026-09-09, "Authorization inside a function?"):
|
|
592
|
+
* function code acts as the system. The invocation gate is the authorization,
|
|
593
|
+
* and inside a function every platform call carries the app's own authority in
|
|
594
|
+
* every family. A capability is therefore never a statement about DATA — a
|
|
595
|
+
* model, a prompt, a channel, a member — because the function may reach all of
|
|
596
|
+
* it. It is declared only where it configures something:
|
|
597
|
+
*
|
|
598
|
+
* `integration:<key>` the egress allowlist — which upstream hosts the
|
|
599
|
+
* function's outbound calls may reach;
|
|
600
|
+
* `secret:<NAME>` credential least privilege — which secret VALUES may
|
|
601
|
+
* cross into the sandbox at all;
|
|
602
|
+
* the high-blast list {@link HIGH_BLAST_CAPABILITIES} — the operations
|
|
603
|
+
* whose blast radius the intent keeps opt-in.
|
|
604
|
+
*
|
|
605
|
+
* Every other string a function used to declare is RETIRED, and the grammar
|
|
606
|
+
* says so by name: an author who still writes `database:orders/Order:read`
|
|
607
|
+
* is told the model changed and what stays, not "unknown family", which would
|
|
608
|
+
* send them to check their spelling.
|
|
589
609
|
*
|
|
590
610
|
* ── Why the components have a charset ────────────────────────────────────
|
|
591
611
|
*
|
|
592
|
-
*
|
|
593
|
-
* a
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
* or a grant means two things at once. It is not injective for the identifiers
|
|
597
|
-
* the repository accepts today: `database-type-config-controller.ts` forbids
|
|
598
|
-
* only `#` in a type key, and js-bao's TOML loader preserves model names
|
|
599
|
-
* verbatim, so `(type "a", model "b/c")` and `(type "a/b", model "c")` both
|
|
600
|
-
* spell `database:a/b/c:read` (D3182-001).
|
|
601
|
-
*
|
|
602
|
-
* The fix is a charset rather than an escaping scheme: both components must
|
|
603
|
-
* match `[A-Za-z0-9_-]+`, so neither delimiter can enter a component and the
|
|
604
|
-
* grammar is injective by construction. Authored TOML stays readable, and a
|
|
605
|
-
* database type whose key carries a delimiter is simply UNREACHABLE from
|
|
606
|
-
* functions — fail closed, with an error that says why rather than a grant
|
|
607
|
-
* that quietly covers a neighbour.
|
|
612
|
+
* A keyed grant's key must match `[A-Za-z0-9_-]+`, so neither `:` nor `/` can
|
|
613
|
+
* enter a component and the string is INJECTIVE — one string, one object. An
|
|
614
|
+
* integration or secret whose key carries a delimiter is simply unreachable
|
|
615
|
+
* from functions, with an error that says why (D3182-001's argument, kept).
|
|
608
616
|
*
|
|
609
617
|
* ── Why this module is pure ──────────────────────────────────────────────
|
|
610
618
|
*
|
|
611
|
-
*
|
|
612
|
-
* the error against their own file, and by the server, which is
|
|
613
|
-
* because the raw admin API exists. Two enforcement points must
|
|
614
|
-
* grammars, so the grammar lives here, in the dependency-free
|
|
619
|
+
* Capabilities are validated twice — by `config push`'s preflight, so an
|
|
620
|
+
* author sees the error against their own file, and by the server, which is
|
|
621
|
+
* authoritative because the raw admin API exists. Two enforcement points must
|
|
622
|
+
* not be two grammars, so the grammar lives here, in the dependency-free
|
|
615
623
|
* `src/config-surface/` tree the CLI vendors at build time
|
|
616
624
|
* (`cli/scripts/gen-config-surfaces.mjs`). The server imports this module; the
|
|
617
625
|
* CLI imports the generated copy; the drift guard fails if they differ.
|
|
@@ -621,117 +629,120 @@ export declare const GRANT_COMPONENT_PATTERN: RegExp;
|
|
|
621
629
|
/** `ServerFunctionConfig.capabilities` is a StringSet with these bounds. */
|
|
622
630
|
export declare const MAX_CAPABILITY_ENTRIES = 100;
|
|
623
631
|
export declare const MAX_CAPABILITY_ENTRY_LENGTH = 200;
|
|
624
|
-
/** The whole grammar, in one expression. */
|
|
625
|
-
export declare const DATABASE_GRANT_PATTERN: RegExp;
|
|
626
|
-
export type GrantAccess = "read" | "write";
|
|
627
632
|
/**
|
|
628
|
-
* The
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
*
|
|
632
|
-
* KEYED `integration:<key>`, `prompt:<key>`, `secret:<NAME>`, `var:<NAME>`
|
|
633
|
-
* — one component under {@link GRANT_COMPONENT_PATTERN}, naming a
|
|
634
|
-
* single object. Every key format the platform issues fits: an
|
|
635
|
-
* integration or prompt key is `^[a-z0-9][a-z0-9-_]{2,}$` and a
|
|
636
|
-
* secret or config-var name is `^[A-Z][A-Z0-9_]{0,63}$`.
|
|
637
|
-
* EXACT `users:send`, `connections:send` — no key component at all. The
|
|
638
|
-
* design doc spells them this way, and the capability really is
|
|
639
|
-
* app-wide: a send names its target at CALL time, so there is no
|
|
640
|
-
* object for the grant to name.
|
|
641
|
-
*
|
|
642
|
-
* `database:` keeps its own two-component grammar, unchanged.
|
|
643
|
-
*
|
|
644
|
-
* `var:` is this child's choice (D3183-001). The intent is silent on config
|
|
645
|
-
* vars; the family is named after the `{{vars.*}}` template namespace the
|
|
646
|
-
* integration proxy already resolves, so an author who has written
|
|
647
|
-
* `{{vars.REGION}}` reads `var:REGION` without learning a second word for the
|
|
648
|
-
* same thing.
|
|
633
|
+
* The two keyed families the intent keeps: one component under
|
|
634
|
+
* {@link GRANT_COMPONENT_PATTERN}, naming a single object. Every key format the
|
|
635
|
+
* platform issues fits: an integration key is `^[a-z0-9][a-z0-9-_]{2,}$` and a
|
|
636
|
+
* secret name is `^[A-Z][A-Z0-9_]{0,63}$`.
|
|
649
637
|
*/
|
|
650
|
-
export declare const KEYED_GRANT_FAMILIES: readonly ["integration", "
|
|
638
|
+
export declare const KEYED_GRANT_FAMILIES: readonly ["integration", "secret"];
|
|
651
639
|
/**
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
640
|
+
* The high-blast-radius opt-ins — #3279, criterion 5 (CR3279-001, D3279-003).
|
|
641
|
+
*
|
|
642
|
+
* Since function code acts as the system, admission to a family is no longer
|
|
643
|
+
* an authority statement: everything the gateway lets through runs with the
|
|
644
|
+
* app's own authority. Most operations are fine that way — that is the whole
|
|
645
|
+
* decision. A short list is not, and the intent names its categories: delete a
|
|
646
|
+
* database, app or user; role changes; secret changes; resource provisioning.
|
|
647
|
+
* Those stay opt-in, so a function that can do them says so in a reviewable
|
|
648
|
+
* line of its TOML.
|
|
649
|
+
*
|
|
650
|
+
* The strings are EXACT and 1:1 with the operation id, so there is nothing to
|
|
651
|
+
* look up: `users.setRole` needs `users:setRole`. They parse with the verb as
|
|
652
|
+
* their KEY, because two exact capabilities in one family must not cover each
|
|
653
|
+
* other — `databases:create` is not permission to delete a database.
|
|
654
|
+
*
|
|
655
|
+
* The database ROLE mutations are here because they hand out persistent
|
|
656
|
+
* authority: a group grant assigns the manager role (D3279-003). App deletion
|
|
657
|
+
* and secret writes have no app-API route today; they are recorded, not gated,
|
|
658
|
+
* and the profile generator refuses to admit a future such route without a row
|
|
659
|
+
* here (`HIGH_BLAST_WATCH` in `scripts/lib/function-profile.mjs`).
|
|
665
660
|
*/
|
|
666
|
-
export declare const
|
|
667
|
-
|
|
668
|
-
export
|
|
669
|
-
export
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
661
|
+
export declare const HIGH_BLAST_CAPABILITIES: readonly ["databases:create", "databases:delete", "databases:transferOwnership", "databases:addManager", "databases:revokePermission", "databases:grantGroupPermission", "databases:revokeGroupPermission", "users:remove", "users:setRole", "blobBuckets:createBucket", "blobBuckets:deleteBucket"];
|
|
662
|
+
/** The exact strings, which since #3279 are exactly the high-blast list. */
|
|
663
|
+
export declare const EXACT_GRANT_STRINGS: readonly ["databases:create", "databases:delete", "databases:transferOwnership", "databases:addManager", "databases:revokePermission", "databases:grantGroupPermission", "databases:revokeGroupPermission", "users:remove", "users:setRole", "blobBuckets:createBucket", "blobBuckets:deleteBucket"];
|
|
664
|
+
export type GrantFamily = (typeof KEYED_GRANT_FAMILIES)[number] | "databases" | "users" | "blobBuckets";
|
|
665
|
+
/**
|
|
666
|
+
* The retired FAMILIES (#3279), each with the reason it is gone.
|
|
667
|
+
*
|
|
668
|
+
* The value is what the family used to authorize, phrased as what a function
|
|
669
|
+
* now reaches without it. A refusal names the authored string, says it is
|
|
670
|
+
* retired, gives this reason, tells the author to delete the entry, and lists
|
|
671
|
+
* what stays.
|
|
672
|
+
*/
|
|
673
|
+
export declare const RETIRED_GRANT_FAMILIES: Record<string, string>;
|
|
674
|
+
/** The retired EXACT strings (#3279), on the same terms. */
|
|
675
|
+
export declare const RETIRED_GRANT_STRINGS: Record<string, string>;
|
|
676
|
+
/**
|
|
677
|
+
* The retirement sentence for one authored string, or null when the string is
|
|
678
|
+
* not a retired one.
|
|
679
|
+
*
|
|
680
|
+
* Exported so the enforcement-time reader (`loadConfigGrants`) can tell a
|
|
681
|
+
* retired string on a pre-change row — tolerated, logged — from a string that
|
|
682
|
+
* was never a grant at all, which still authorizes nothing.
|
|
683
|
+
*/
|
|
684
|
+
export declare function retiredGrantRefusal(raw: string): string | null;
|
|
676
685
|
/**
|
|
677
686
|
* Any grant, in one flat shape.
|
|
678
687
|
*
|
|
679
|
-
* Flat rather than a discriminated union
|
|
680
|
-
*
|
|
681
|
-
*
|
|
682
|
-
* then the field that family populates, and the fields another family would
|
|
683
|
-
* have used are simply absent.
|
|
688
|
+
* Flat rather than a discriminated union because this module is vendored into
|
|
689
|
+
* the CLI, which compiles with `strict: false`: a caller reads `family` and
|
|
690
|
+
* then `key`, and the fields another family would have used are simply absent.
|
|
684
691
|
*/
|
|
685
692
|
export interface FunctionGrant {
|
|
686
693
|
family: GrantFamily;
|
|
687
694
|
/**
|
|
688
|
-
* The single component of a keyed family — the integration key
|
|
689
|
-
* secret name or
|
|
690
|
-
*
|
|
695
|
+
* The single component of a keyed family — the integration key or the
|
|
696
|
+
* secret name — or the VERB of a high-blast string (`delete` for
|
|
697
|
+
* `databases:delete`), so that no two exact capabilities in one family read
|
|
698
|
+
* as the same grant.
|
|
691
699
|
*/
|
|
692
700
|
key: string;
|
|
693
|
-
/** `database:` only. */
|
|
694
|
-
databaseType?: string;
|
|
695
|
-
modelName?: string;
|
|
696
|
-
access?: GrantAccess;
|
|
697
701
|
/** The authored string, so an error or a log line can quote it. */
|
|
698
702
|
raw: string;
|
|
699
703
|
}
|
|
700
|
-
export interface ParsedGrant {
|
|
701
|
-
grant?: DatabaseGrant;
|
|
702
|
-
error?: string;
|
|
703
|
-
}
|
|
704
704
|
export interface ParsedFunctionGrant {
|
|
705
705
|
grant?: FunctionGrant;
|
|
706
706
|
error?: string;
|
|
707
707
|
}
|
|
708
708
|
/**
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
709
|
+
* A channel name's ceiling, and the reason it has one.
|
|
710
|
+
*
|
|
711
|
+
* The name rides in a `ConnectionMapping` row's document-id slot as
|
|
712
|
+
* `ch:<appId>:<channel>` and in every grant token's claims, so an unbounded
|
|
713
|
+
* name would be an unbounded key and an unbounded credential. 200 characters
|
|
714
|
+
* is {@link MAX_CAPABILITY_ENTRY_LENGTH}, kept for continuity with the rows
|
|
715
|
+
* #3184 already wrote.
|
|
714
716
|
*/
|
|
715
|
-
export declare
|
|
717
|
+
export declare const MAX_CHANNEL_NAME_LENGTH = 200;
|
|
718
|
+
export interface ParsedChannelName {
|
|
719
|
+
/** The segment before the first `:`. Absent when the name is refused. */
|
|
720
|
+
namespace?: string;
|
|
721
|
+
error?: string;
|
|
722
|
+
}
|
|
716
723
|
/**
|
|
717
|
-
*
|
|
724
|
+
* A channel NAME, and its namespace.
|
|
718
725
|
*
|
|
719
|
-
* The
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
726
|
+
* The grammar is the grant component charset applied per segment: one or more
|
|
727
|
+
* `[A-Za-z0-9_-]+` segments joined by `:`. The `channel:<namespace>` GRANT
|
|
728
|
+
* that used to cover a name is retired (#3279); the name grammar stays because
|
|
729
|
+
* the authorize and publish routes answer 400 about a name outside it before
|
|
730
|
+
* anything else, and the connection worker keys membership by it.
|
|
731
|
+
*
|
|
732
|
+
* Refusals name the segment that failed, because "channel name is invalid" on
|
|
733
|
+
* a name like `orders:a::b` tells an author nothing they cannot already see.
|
|
725
734
|
*/
|
|
726
|
-
export declare function
|
|
735
|
+
export declare function parseChannelName(value: unknown): ParsedChannelName;
|
|
727
736
|
/**
|
|
728
|
-
* One capability entry as a
|
|
737
|
+
* One capability entry as a grant, or the reason it is not one.
|
|
729
738
|
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
739
|
+
* The single entry point the enforcement path and both preflights use: a
|
|
740
|
+
* caller holds one authored string and asks what it grants, without having to
|
|
741
|
+
* know which of the shapes to try. Every diagnosis is specific — a retired
|
|
742
|
+
* string gets the retirement and what stays; a near miss in a kept family gets
|
|
743
|
+
* the family's own strings; a keyed family gets its charset rule.
|
|
733
744
|
*/
|
|
734
|
-
export declare function
|
|
745
|
+
export declare function parseFunctionGrant(entry: unknown): ParsedFunctionGrant;
|
|
735
746
|
/**
|
|
736
747
|
* One shape rather than a discriminated union: this module is vendored into
|
|
737
748
|
* the CLI, which compiles with `strict: false`, where narrowing on a boolean
|
|
@@ -742,30 +753,32 @@ export interface ParsedCapabilities {
|
|
|
742
753
|
ok: boolean;
|
|
743
754
|
/** Deduped, in authored order — what the config row stores. Empty when refused. */
|
|
744
755
|
capabilities: string[];
|
|
745
|
-
/**
|
|
746
|
-
* DATABASE grants only, and deliberately so.
|
|
747
|
-
*
|
|
748
|
-
* #3182's call sites — the DO envelope, the typed handle, the records
|
|
749
|
-
* decision, the unscoped-read rule — all read this field and all mean
|
|
750
|
-
* "database". Widening it in place would have handed every one of them five
|
|
751
|
-
* families it has no branch for, silently. The new families arrive beside
|
|
752
|
-
* it in {@link allGrants}, so a caller that wants them asks for them.
|
|
753
|
-
*/
|
|
754
|
-
grants: DatabaseGrant[];
|
|
755
|
-
/** Every grant, all families, in authored order. */
|
|
756
|
+
/** Every grant, in authored order. */
|
|
756
757
|
allGrants: FunctionGrant[];
|
|
757
758
|
/**
|
|
758
|
-
* The
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
* asks "which platform capabilities does this version hold" and gets exactly
|
|
762
|
-
* those strings, without filtering a mixed list itself.
|
|
759
|
+
* The retired strings that were SKIPPED, in authored order — populated only
|
|
760
|
+
* under `tolerateRetired` (see {@link parseCapabilities}); a strict parse
|
|
761
|
+
* refuses them instead and leaves this empty.
|
|
763
762
|
*/
|
|
764
|
-
|
|
765
|
-
hasReadGrant: boolean;
|
|
763
|
+
retired: string[];
|
|
766
764
|
/** Empty when accepted. */
|
|
767
765
|
errors: string[];
|
|
768
766
|
}
|
|
767
|
+
export interface ParseCapabilitiesOptions {
|
|
768
|
+
/**
|
|
769
|
+
* Skip retired strings instead of refusing them — #3279 edge 25.
|
|
770
|
+
*
|
|
771
|
+
* The PUSH path is strict: a file that still declares a retired grant is
|
|
772
|
+
* refused with the retirement, because a line that is accepted and ignored
|
|
773
|
+
* is a line the author believes means something. The ENFORCEMENT path is
|
|
774
|
+
* tolerant: a config version pushed before #3279 carries retired strings in
|
|
775
|
+
* a row that can never be re-pushed to fix (its envelope is immutable), and
|
|
776
|
+
* refusing it there would break every function pushed before the change.
|
|
777
|
+
* Such a row authorizes exactly what it keeps, and the skipped strings are
|
|
778
|
+
* reported in `retired` so the caller can log them.
|
|
779
|
+
*/
|
|
780
|
+
tolerateRetired?: boolean;
|
|
781
|
+
}
|
|
769
782
|
/**
|
|
770
783
|
* A whole `capabilities` list: shape, bounds, grammar, duplicates.
|
|
771
784
|
*
|
|
@@ -773,65 +786,16 @@ export interface ParsedCapabilities {
|
|
|
773
786
|
* field, so an over-long entry is a named push error instead of a late model
|
|
774
787
|
* throw after the R2 object has already been written (principle 6).
|
|
775
788
|
*/
|
|
776
|
-
export declare function parseCapabilities(value: unknown): ParsedCapabilities;
|
|
777
|
-
/**
|
|
778
|
-
* The unscoped-read rule, from the intent's database-authorization decision:
|
|
779
|
-
* "a member-accessible function whose database read has no `$caller` binding
|
|
780
|
-
* needs an explicit unscoped-read flag in its config, and that flag is the
|
|
781
|
-
* human-reviewed artifact. From phase 2, any member-reachable function holding
|
|
782
|
-
* a database read grant carries the flag."
|
|
783
|
-
*
|
|
784
|
-
* Every HTTP-invocable function is member-reachable behind its access gate, and
|
|
785
|
-
* in this phase every function is HTTP-invocable, so the rule applies to every
|
|
786
|
-
* config declaring a read grant — a fail-closed superset of the intent's rule,
|
|
787
|
-
* which a later child may narrow for trigger-only functions.
|
|
788
|
-
*/
|
|
789
|
-
export declare function unscopedReadsRefusal(grants: readonly DatabaseGrant[], unscopedReads: boolean): string | null;
|
|
789
|
+
export declare function parseCapabilities(value: unknown, options?: ParseCapabilitiesOptions): ParsedCapabilities;
|
|
790
790
|
/**
|
|
791
|
-
*
|
|
792
|
-
* schema declares, or that it declares no schema, or that the schema it does
|
|
793
|
-
* declare could not be read.
|
|
794
|
-
*
|
|
795
|
-
* The third state is the reason this is an interface rather than a bare
|
|
796
|
-
* `models` field. "No schema" and "a schema nobody can parse" are not the same
|
|
797
|
-
* fact, and collapsing them makes an unparseable schema authorize every model
|
|
798
|
-
* name an author cares to write (SO3182-001) — the widest possible answer to a
|
|
799
|
-
* question the platform failed to evaluate.
|
|
800
|
-
*/
|
|
801
|
-
export interface KnownGrantTargetType {
|
|
802
|
-
/** Model names the schema declares, or null when it declares no schema. */
|
|
803
|
-
models: ReadonlySet<string> | null;
|
|
804
|
-
/** True when a stored/authored schema exists but could not be parsed. */
|
|
805
|
-
schemaUnreadable?: boolean;
|
|
806
|
-
}
|
|
807
|
-
/**
|
|
808
|
-
* What a grant may name: a database type the app has, and — when that type
|
|
809
|
-
* declares a schema — a model the schema declares.
|
|
810
|
-
*
|
|
811
|
-
* `models: null` is a type with NO schema. The DatabaseDO is schemaless, so a
|
|
812
|
-
* grant on such a type is accepted by grammar and simply carries no typed
|
|
813
|
-
* handle; refusing it would make an untyped database ungrantable.
|
|
791
|
+
* The config-tree objects an `integration:` grant may name.
|
|
814
792
|
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
* `database-type-configs/*.toml`, the server reads the app's rows — and the
|
|
822
|
-
* RULE is what must not differ.
|
|
823
|
-
*/
|
|
824
|
-
export declare function validateGrantsAgainstTypes(grants: readonly DatabaseGrant[], types: ReadonlyMap<string, KnownGrantTargetType>): string[];
|
|
825
|
-
/**
|
|
826
|
-
* The config-tree objects an `integration:` or `prompt:` grant may name.
|
|
827
|
-
*
|
|
828
|
-
* Only the two families whose targets are CONFIG-TREE state. `secret:` and
|
|
829
|
-
* `var:` are absent on purpose (D3183-002): their values are provisioned per
|
|
830
|
-
* environment, out of band, and are not part of the reviewed tree at all, so
|
|
831
|
-
* the ordinary order of work is to push the function and then provision the
|
|
832
|
-
* value. Refusing an unprovisioned name at push would break that; a missing
|
|
833
|
-
* value at CALL time is a structured runtime error instead. `users:send` and
|
|
834
|
-
* `connections:send` name no object.
|
|
793
|
+
* Only the one family whose target is CONFIG-TREE state. `secret:` is absent
|
|
794
|
+
* on purpose (D3183-002): its values are provisioned per environment, out of
|
|
795
|
+
* band, and are not part of the reviewed tree at all, so the ordinary order of
|
|
796
|
+
* work is to push the function and then provision the value. Refusing an
|
|
797
|
+
* unprovisioned name at push would break that; a missing value at CALL time is
|
|
798
|
+
* a structured runtime error instead. The high-blast strings name no object.
|
|
835
799
|
*
|
|
836
800
|
* `null` means "this enforcement point could not find out". The CLI reads the
|
|
837
801
|
* tree and, when it can, a live listing; when neither is available it defers
|
|
@@ -842,11 +806,9 @@ export declare function validateGrantsAgainstTypes(grants: readonly DatabaseGran
|
|
|
842
806
|
export interface KnownGrantTargets {
|
|
843
807
|
/** Non-archived integration keys, or null when unknown. */
|
|
844
808
|
integrations: ReadonlySet<string> | null;
|
|
845
|
-
/** Non-archived prompt keys, or null when unknown. */
|
|
846
|
-
prompts: ReadonlySet<string> | null;
|
|
847
809
|
}
|
|
848
810
|
/**
|
|
849
|
-
* Grants naming an integration
|
|
811
|
+
* Grants naming an integration the app does not have.
|
|
850
812
|
*
|
|
851
813
|
* One message per offending grant, quoting the whole authored string AND the
|
|
852
814
|
* key on its own, so an operator reading the line knows both what to fix in
|
|
@@ -868,23 +830,20 @@ export declare function validateKeyedGrantsAgainstTargets(grants: readonly Funct
|
|
|
868
830
|
*
|
|
869
831
|
* ── What the manifest is for ─────────────────────────────────────────────
|
|
870
832
|
*
|
|
871
|
-
*
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
*
|
|
875
|
-
*
|
|
876
|
-
*
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
* the bundle (principle 8).
|
|
833
|
+
* Observability, and nothing else. `primitive functions get` and the admin
|
|
834
|
+
* get list what a version registered, so an operator can see it without
|
|
835
|
+
* reading the bundle (principle 8). The read rule it used to be decided from
|
|
836
|
+
* (#3187's `$caller` relaxation of `unscopedReads`) is retired by #3279:
|
|
837
|
+
* function code acts as the system, so there is no per-model grant for a
|
|
838
|
+
* binding to waive. The intent says so in as many words — "the models a
|
|
839
|
+
* function touches are collected at push as a reviewable manifest, not an
|
|
840
|
+
* authorization".
|
|
880
841
|
*
|
|
881
842
|
* It is HONEST-CODE evidence and the design doc says so: a hostile bundle can
|
|
882
843
|
* register whatever it likes, because the collector runs the tenant's own
|
|
883
|
-
* code. Nothing security-relevant at runtime reads it — parameter injection
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
* the manifest buys is protection against BUGS, which is the bound the design
|
|
887
|
-
* doc states and this comment repeats so nobody has to rediscover it.
|
|
844
|
+
* code. Nothing security-relevant at runtime reads it — parameter injection
|
|
845
|
+
* and cache verification happen in the platform-owned SDK inside the isolate,
|
|
846
|
+
* and the invocation gate remains the adversarial boundary.
|
|
888
847
|
*
|
|
889
848
|
* ── Why the grammar is here ──────────────────────────────────────────────
|
|
890
849
|
*
|
|
@@ -893,11 +852,23 @@ export declare function validateKeyedGrantsAgainstTargets(grants: readonly Funct
|
|
|
893
852
|
* authoritatively because the raw admin API exists. Two enforcement points,
|
|
894
853
|
* one rule, in the dependency-free tree the CLI vendors.
|
|
895
854
|
*/
|
|
896
|
-
/**
|
|
897
|
-
|
|
855
|
+
/**
|
|
856
|
+
* The manifest encoding, versioned with the envelope that carries it.
|
|
857
|
+
*
|
|
858
|
+
* Version 2 (#3279 behavior 10) adds what a function TOUCHES beside what it
|
|
859
|
+
* registers: the deduplicated `models` its code names and the `families` of
|
|
860
|
+
* `ctx.api` and the ctx helpers it reaches, collected by a static scan of the
|
|
861
|
+
* built bundle, plus a `dynamicModels` marker for a model name the scan
|
|
862
|
+
* could not resolve — distinct from an empty list, which means "names none".
|
|
863
|
+
* A version-1 manifest still parses: it records registrations only.
|
|
864
|
+
*/
|
|
865
|
+
export declare const FUNCTION_MANIFEST_SCHEMA_VERSION = 2;
|
|
866
|
+
export declare const FUNCTION_MANIFEST_SCHEMA_VERSIONS: readonly [1, 2];
|
|
898
867
|
/** Bounds, so a manifest cannot be a way to store an unbounded blob. */
|
|
899
868
|
export declare const MAX_MANIFEST_QUERIES = 200;
|
|
900
869
|
export declare const MAX_MANIFEST_NAME_LENGTH = 120;
|
|
870
|
+
export declare const MAX_MANIFEST_MODELS = 200;
|
|
871
|
+
export declare const MAX_MANIFEST_FAMILIES = 40;
|
|
901
872
|
export interface ManifestParam {
|
|
902
873
|
type?: string;
|
|
903
874
|
caller?: boolean;
|
|
@@ -918,6 +889,21 @@ export interface ManifestQuery {
|
|
|
918
889
|
export interface FunctionManifest {
|
|
919
890
|
schemaVersion: number;
|
|
920
891
|
queries: ManifestQuery[];
|
|
892
|
+
/**
|
|
893
|
+
* Every model name the bundle names, deduplicated and sorted: registration
|
|
894
|
+
* declarations, literal `.model("…")` calls, and literal `modelName`/`model`
|
|
895
|
+
* arguments of the direct records and documents calls (#3279). Empty for a
|
|
896
|
+
* version-1 manifest, and for a function that names none.
|
|
897
|
+
*/
|
|
898
|
+
models: string[];
|
|
899
|
+
/** Every `ctx.api.<family>` and ctx-helper family the bundle reaches. */
|
|
900
|
+
families: string[];
|
|
901
|
+
/**
|
|
902
|
+
* The scan met a model name it could not resolve — a computed
|
|
903
|
+
* `modelName`, a `.model(variable)`. Says "and possibly more", which an
|
|
904
|
+
* empty `models` list does not.
|
|
905
|
+
*/
|
|
906
|
+
dynamicModels: boolean;
|
|
921
907
|
}
|
|
922
908
|
export interface ParsedManifest {
|
|
923
909
|
ok: boolean;
|
|
@@ -932,40 +918,6 @@ export interface ParsedManifest {
|
|
|
932
918
|
* and whether the author meant any of it are questions this cannot answer.
|
|
933
919
|
*/
|
|
934
920
|
export declare function parseFunctionManifest(value: unknown): ParsedManifest;
|
|
935
|
-
/**
|
|
936
|
-
* The phase-4 relaxation of the unscoped-read rule, from the intent: "from
|
|
937
|
-
* phase 4 a recorded `$caller` binding on every read satisfies the rule
|
|
938
|
-
* instead".
|
|
939
|
-
*
|
|
940
|
-
* Returns the refusal, or null when the config may be pushed without
|
|
941
|
-
* `unscopedReads = true`.
|
|
942
|
-
*
|
|
943
|
-
* Fail-closed in three ways, each of which is a way an author could otherwise
|
|
944
|
-
* get the flag waived without the property it stands for:
|
|
945
|
-
*
|
|
946
|
-
* - no manifest at all is no evidence, so the flag is still required;
|
|
947
|
-
* - a manifest naming NO query that touches a read-granted model records
|
|
948
|
-
* nothing about how those models are read, so it satisfies nothing;
|
|
949
|
-
* - one non-caller-scoped registration over a read-granted model is enough
|
|
950
|
-
* to require the flag, because that query reads every row for every
|
|
951
|
-
* caller — which is exactly what the flag declares.
|
|
952
|
-
*/
|
|
953
|
-
export declare function callerScopedReadsRefusal(grants: readonly DatabaseGrant[], unscopedReads: boolean, manifest: FunctionManifest | null): string | null;
|
|
954
|
-
/**
|
|
955
|
-
* Whether a running version's raw (non-query) reads must be refused inside the
|
|
956
|
-
* sandbox — #3187 behavior 23.
|
|
957
|
-
*
|
|
958
|
-
* True exactly when the version was accepted under the relaxation: it holds a
|
|
959
|
-
* database READ grant and did not declare `unscopedReads`. Such a version was
|
|
960
|
-
* pushed on the strength of its manifest saying every read is caller-scoped,
|
|
961
|
-
* so the SDK admits the caller-scoped registered reads and refuses the rest.
|
|
962
|
-
*
|
|
963
|
-
* Honest-code protection at the design doc's stated adequacy: the SDK is
|
|
964
|
-
* platform-owned, but a hostile bundle is not obliged to use it. The gateway
|
|
965
|
-
* and DatabaseDO grant checks (#3182) are unchanged and remain the adversarial
|
|
966
|
-
* boundary.
|
|
967
|
-
*/
|
|
968
|
-
export declare function callerScopedReadsRequired(capabilities: readonly string[], unscopedReads: boolean): boolean;
|
|
969
921
|
export declare const WORKFLOW_SURFACE: ConfigObjectSurface;
|
|
970
922
|
export declare const PROMPT_SURFACE: ConfigObjectSurface;
|
|
971
923
|
export declare const INTEGRATION_SURFACE: ConfigObjectSurface;
|