@sema-agent/primitives 0.0.0-stage → 7.58.0
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/LICENSE +106 -0
- package/README.md +98 -2
- package/dist/credential-ref.d.ts +89 -0
- package/dist/credential-ref.js +125 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +33 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
Parameters
|
|
4
|
+
|
|
5
|
+
Licensor: clay (github.com/clayboby)
|
|
6
|
+
Licensed Work: @sema-agent/core (sema-core) and the satellite packages
|
|
7
|
+
published from this repository (currently
|
|
8
|
+
@sema-agent/mods-kernel, @sema-agent/primitives and @sema-agent/core-stores-sql)
|
|
9
|
+
(formerly published as @sema-ai/core)
|
|
10
|
+
The Licensed Work is (c) 2026 clay.
|
|
11
|
+
Additional Use Grant: You may make production use of the Licensed Work for
|
|
12
|
+
personal, educational, research, or other
|
|
13
|
+
non-commercial purposes. Commercial production use
|
|
14
|
+
requires a commercial license from the Licensor.
|
|
15
|
+
Change Date: 2030-07-13
|
|
16
|
+
Change License: Apache License, Version 2.0
|
|
17
|
+
|
|
18
|
+
For information about alternative licensing arrangements for the Licensed
|
|
19
|
+
Work, please contact the Licensor.
|
|
20
|
+
|
|
21
|
+
Notice
|
|
22
|
+
|
|
23
|
+
The Business Source License (this document, or the "License") is not an Open
|
|
24
|
+
Source license. However, the Licensed Work will eventually be made available
|
|
25
|
+
under an Open Source License, as stated in this License.
|
|
26
|
+
|
|
27
|
+
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
|
|
28
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
29
|
+
|
|
30
|
+
-----------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
Terms
|
|
33
|
+
|
|
34
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
35
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
36
|
+
Licensor may make an Additional Use Grant, above, permitting limited
|
|
37
|
+
production use.
|
|
38
|
+
|
|
39
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
40
|
+
available distribution of a specific version of the Licensed Work under this
|
|
41
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
42
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
43
|
+
above terminate.
|
|
44
|
+
|
|
45
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
46
|
+
currently in effect as described in this License, you must purchase a
|
|
47
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
48
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
49
|
+
|
|
50
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
51
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
52
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
53
|
+
for each version of the Licensed Work released by Licensor.
|
|
54
|
+
|
|
55
|
+
You must conspicuously display this License on each original or modified copy
|
|
56
|
+
of the Licensed Work. If you receive the Licensed Work in original or
|
|
57
|
+
modified form from a third party, the terms and conditions set forth in this
|
|
58
|
+
License apply to your use of that work.
|
|
59
|
+
|
|
60
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
61
|
+
terminate your rights under this License for the current and all other
|
|
62
|
+
versions of the Licensed Work.
|
|
63
|
+
|
|
64
|
+
This License does not grant you any right in any trademark or logo of
|
|
65
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
66
|
+
Licensor as expressly required by this License).
|
|
67
|
+
|
|
68
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
69
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
70
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
71
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
72
|
+
TITLE.
|
|
73
|
+
|
|
74
|
+
MariaDB hereby grants you permission to use this License's text to license
|
|
75
|
+
your works, and to refer to it using the trademark "Business Source License",
|
|
76
|
+
as long as you comply with the Covenants of Licensor below.
|
|
77
|
+
|
|
78
|
+
Covenants of Licensor
|
|
79
|
+
|
|
80
|
+
In consideration of the right to use this License's text and the "Business
|
|
81
|
+
Source License" name and trademark, Licensor covenants to MariaDB, and to all
|
|
82
|
+
other recipients of the licensed work to be provided by Licensor:
|
|
83
|
+
|
|
84
|
+
1. To specify as the Change License the GPL Version 2.0 or any later version,
|
|
85
|
+
or a license that is compatible with GPL Version 2.0 or a later version,
|
|
86
|
+
where "compatible" means that software provided under the Change License can
|
|
87
|
+
be included in a program with software provided under GPL Version 2.0 or a
|
|
88
|
+
later version. Licensor may specify additional Change Licenses without
|
|
89
|
+
limitation.
|
|
90
|
+
|
|
91
|
+
2. To either: (a) specify an additional grant of rights to use that does not
|
|
92
|
+
impose any additional restriction on the right granted in this License, as
|
|
93
|
+
the Additional Use Grant; or (b) insert the text "None".
|
|
94
|
+
|
|
95
|
+
3. To specify a Change Date.
|
|
96
|
+
|
|
97
|
+
4. Not to modify this License in any other way.
|
|
98
|
+
|
|
99
|
+
-----------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
Third-party notices
|
|
102
|
+
|
|
103
|
+
Versions of this package up to and including 1.277.1 were published under the
|
|
104
|
+
MIT License; those published copies remain governed by the license they were
|
|
105
|
+
distributed under. This package incorporates design and (where noted) prompt
|
|
106
|
+
content adapted from MIT-licensed projects — see NOTICE.md for attributions.
|
package/README.md
CHANGED
|
@@ -1,3 +1,99 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sema-agent/primitives
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Low-level definitions of the sema engine that every layer may import: no policy of any layer, no state, no
|
|
4
|
+
dependencies. Today it holds one member, the credential reference. The engine depends on this package and reads it by
|
|
5
|
+
name, so in a project that installs both there is one copy: an acceptance gate, a settings validator or an admin form
|
|
6
|
+
reads the same closed set through the same function object as the engine does. Runs in Node and in browsers; ships its
|
|
7
|
+
own types.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install @sema-agent/primitives
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
ES modules only (`"type": "module"`), Node >= 20.18.1 where Node is used. One entry, `.`; there are no deep paths.
|
|
16
|
+
|
|
17
|
+
## Version
|
|
18
|
+
|
|
19
|
+
`@sema-agent/primitives@X.Y.Z` is cut with `@sema-agent/core@X.Y.Z`: the same version number, built from the same
|
|
20
|
+
commit. A package version moves with every core release whether or not the primitives changed.
|
|
21
|
+
|
|
22
|
+
`@sema-agent/core@X.Y.Z` depends on `@sema-agent/primitives@X.Y.Z` exactly. A project that also imports this package
|
|
23
|
+
names the same version as its core (or a range npm can resolve to it); then `npm ls @sema-agent/primitives` shows one
|
|
24
|
+
copy. Naming another version installs two — npm keeps core's own beside it — and the two answer for different versions.
|
|
25
|
+
|
|
26
|
+
## The credential reference
|
|
27
|
+
|
|
28
|
+
A declaration names a credential by reference instead of carrying its value. The reference is one of a closed set of
|
|
29
|
+
kinds; whoever resolves it (a deployment's seat, not this package) decides what the name means and whether this
|
|
30
|
+
declaration may use it.
|
|
31
|
+
|
|
32
|
+
| `kind` | Members | Request surface (`CREDENTIAL_REF_TRUST[kind].request`) |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `env` | `headers?`: header name → variable name · `env?`: child variable → variable name (at least one) | `refused` |
|
|
35
|
+
| `vault` | `entry`: a stored credential entry | `own_entry` — only an entry that belongs to the submitting principal |
|
|
36
|
+
| `handle` | `id`: an opaque id the deployment minted | `refused` |
|
|
37
|
+
| `assertion` | `audience`: what a short-lived assertion is requested for | `refused` |
|
|
38
|
+
|
|
39
|
+
`readCredentialRef(value)` answers `{ ok: true, ref }` with a fresh frozen copy (forward the copy, never the value you
|
|
40
|
+
read), or `{ ok: false, problem }`. Each fault is its own `reason`, and the first one found is the one reported, in this
|
|
41
|
+
order: not a record; `kind` absent, not text or unknown; an extra member; a missing member; a bad value. The reader does not throw: a value whose members cannot be read (a revoked or throwing Proxy) is
|
|
42
|
+
`not_a_record` (or, inside a name map, that map's `bad_value`). With no stack left a valid value may be refused, under any
|
|
43
|
+
reason, and never an invalid one accepted; the call itself may throw a `RangeError` before it is entered, which no function
|
|
44
|
+
can catch.
|
|
45
|
+
|
|
46
|
+
| `reason` | Meaning |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `not_a_record` | not an ordinary record (a primitive, `null`, an array, a class instance, a `Map`, a `Date`), or one whose members cannot be read; an object whose prototype is none or itself has none — the `Object.prototype` of any realm is one — is a record |
|
|
49
|
+
| `unknown_kind` | `kind` is text outside `CREDENTIAL_REF_KINDS` (every spelling the set does not hold: another case, padded, zero-width, full-width, a look-alike letter) |
|
|
50
|
+
| `extra_field` | a member the kind does not have (a member spelled in another case is one) |
|
|
51
|
+
| `missing_field` | `kind`, or a member the kind needs, is absent (for `env`: neither `headers` nor `env`, field `headers\|env`) |
|
|
52
|
+
| `bad_value` | a member of the wrong form (below) |
|
|
53
|
+
|
|
54
|
+
A member is an own enumerable string-keyed data property. A getter is refused and never called, nothing is read through a
|
|
55
|
+
prototype, a key present with the value `undefined` is a bad value (not an absent member), and a non-enumerable or
|
|
56
|
+
symbol-keyed member is not read and not copied.
|
|
57
|
+
|
|
58
|
+
`bad_value` is: for `kind`, anything but text; for a text member (`entry`, `id`, `audience`), anything but 1 to
|
|
59
|
+
`CREDENTIAL_REF_TEXT_MAX` (2048) UTF-16 code units with no control, format or line-separator character (C0 and C1,
|
|
60
|
+
zero-width, bidi override, soft hyphen, BOM, U+2028 / U+2029), no default-ignorable, noncharacter or private-use character, no lone surrogate and no leading or trailing white space;
|
|
61
|
+
for a name map (`headers`, `env`), anything but a record of 1 to `CREDENTIAL_REF_MAP_MAX` (64) entries whose names are
|
|
62
|
+
within the grammar (a header name is an HTTP token; a child variable name and every variable name is
|
|
63
|
+
`[A-Za-z_][A-Za-z0-9_]*`) and within the text bound, and — for `headers`, whose names are case-insensitive — in which no
|
|
64
|
+
two names differ only by case. The maps come back frozen and without a prototype: an absent name reads `undefined`, and an
|
|
65
|
+
own `__proto__` name is a plain key. Text is compared as written; nothing is normalised (two spellings of one visible
|
|
66
|
+
string are two entries; visually confusable text — homoglyphs, a braille blank, stacked combining marks — is not judged, and
|
|
67
|
+
an emoji presentation selector or a zero-width joiner is refused, because these members are identifiers, not prose). The text
|
|
68
|
+
bound counts UTF-16 code units (up to 6144 UTF-8 bytes), and the character classes are the
|
|
69
|
+
host's Unicode tables, so two surfaces on engines with different Unicode versions can differ for a recently added character.
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import { readCredentialRef } from "@sema-agent/primitives";
|
|
73
|
+
|
|
74
|
+
readCredentialRef({ kind: "vault", entry: "github-main" }); // { ok: true, ref: { kind: "vault", entry: "github-main" } }
|
|
75
|
+
readCredentialRef({ kind: "vault2", entry: "x" }); // { ok: false, problem: { reason: "unknown_kind", kind: "vault2" } }
|
|
76
|
+
readCredentialRef({ kind: "handle" }); // { ok: false, problem: { reason: "missing_field", kind: "handle", field: "id" } }
|
|
77
|
+
readCredentialRef({ kind: "handle", id: "h1", note: "x" }); // { ok: false, problem: { reason: "extra_field", kind: "handle", field: "note" } }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
A `kind` or `field` the reader echoes back is the caller's text, cut to 64 characters with control, format and
|
|
81
|
+
line-separator characters and lone surrogates replaced; fence it before rendering it where markup is interpreted. A Proxy's
|
|
82
|
+
own traps (on the value, its maps or its prototype) are the caller's code and run as they would for any property read.
|
|
83
|
+
|
|
84
|
+
The reader judges shape only: it does not know whether a variable exists, whether an entry belongs to anyone, which header
|
|
85
|
+
names a deployment forbids (the protocol's own names, a principal header), which child variables a declaration may set, or
|
|
86
|
+
whether a reference may reach a destination. Those are the policy of the layer that reads the reference.
|
|
87
|
+
|
|
88
|
+
`CREDENTIAL_REF_TRUST` has a row for each kind and for nothing else (it has no prototype, so no inherited member answers);
|
|
89
|
+
look a kind up in it only after `readCredentialRef` has accepted the value.
|
|
90
|
+
|
|
91
|
+
## Exports
|
|
92
|
+
|
|
93
|
+
Each name below is a runtime value or a type exported from the package root. The root's declarations are the contract; a
|
|
94
|
+
removed or re-kinded export is named as breaking in the `@sema-agent/core` release notes.
|
|
95
|
+
|
|
96
|
+
| Group | Names |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| Credential reference | `readCredentialRef` `CREDENTIAL_REF_KINDS` `CREDENTIAL_REF_TRUST` `CREDENTIAL_REF_TEXT_MAX` `CREDENTIAL_REF_MAP_MAX` `CREDENTIAL_REF_PROBLEM_REASONS` |
|
|
99
|
+
| Types | `CredentialRef` `CredentialRefKind` `CredentialRefRequestAdmission` `CredentialRefProblem` `CredentialRefProblemReason` `CredentialRefReading` |
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The credential reference: how a declaration names a credential without carrying one. This module says which references
|
|
3
|
+
* exist and whether a value is one, so the engine's door, an acceptance gate, a settings validator and an admin form read
|
|
4
|
+
* one closed set through one function. It resolves nothing, reads no environment and holds no state.
|
|
5
|
+
* Zero dependencies: no builtin, no import, so the same file runs in Node and in a browser.
|
|
6
|
+
*/
|
|
7
|
+
/** The closed set of kinds. A value whose `kind` is not in it is refused, never guessed at. */
|
|
8
|
+
export declare const CREDENTIAL_REF_KINDS: readonly ["env", "vault", "handle", "assertion"];
|
|
9
|
+
export type CredentialRefKind = (typeof CREDENTIAL_REF_KINDS)[number];
|
|
10
|
+
/**
|
|
11
|
+
* A reference, by kind. `env` names variables of the resolver's environment (header name → variable name for an http
|
|
12
|
+
* peer, child variable → variable name for a stdio peer; the maps have no prototype); `vault` names a stored entry;
|
|
13
|
+
* `handle` is an opaque id the deployment minted; `assertion` names the audience a short-lived assertion is requested for.
|
|
14
|
+
*/
|
|
15
|
+
export type CredentialRef = {
|
|
16
|
+
readonly kind: "env";
|
|
17
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
18
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: "vault";
|
|
21
|
+
readonly entry: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "handle";
|
|
24
|
+
readonly id: string;
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: "assertion";
|
|
27
|
+
readonly audience: string;
|
|
28
|
+
};
|
|
29
|
+
/** What an acceptance gate may do with a reference that arrives on the request surface (the deployment may write any kind). */
|
|
30
|
+
export type CredentialRefRequestAdmission = "refused" | "own_entry";
|
|
31
|
+
type TrustRow = {
|
|
32
|
+
readonly request: CredentialRefRequestAdmission;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Where each kind may come from. `refused`: a request surface may not carry it (an environment name would read any
|
|
36
|
+
* variable of the resolver's process, a handle names someone else's credential, an assertion is minted by the deployment);
|
|
37
|
+
* `own_entry`: only for an entry that belongs to the submitting principal. A row per kind and nothing else (no prototype);
|
|
38
|
+
* look a kind up only after `readCredentialRef` accepted the value. A shape for a gate to read; it authenticates nothing.
|
|
39
|
+
*/
|
|
40
|
+
export declare const CREDENTIAL_REF_TRUST: Readonly<Record<CredentialRefKind, TrustRow>>;
|
|
41
|
+
/** The longest text member (entry, id, audience, header name, variable name) a reference may carry, in UTF-16 code units. */
|
|
42
|
+
export declare const CREDENTIAL_REF_TEXT_MAX = 2048;
|
|
43
|
+
/** The most entries one name map (`headers`, `env`) of an `env` reference may hold. */
|
|
44
|
+
export declare const CREDENTIAL_REF_MAP_MAX = 64;
|
|
45
|
+
/** Why a value is not a reference; each reason is its own exit. */
|
|
46
|
+
export declare const CREDENTIAL_REF_PROBLEM_REASONS: readonly ["not_a_record", "unknown_kind", "missing_field", "extra_field", "bad_value"];
|
|
47
|
+
export type CredentialRefProblemReason = (typeof CREDENTIAL_REF_PROBLEM_REASONS)[number];
|
|
48
|
+
/**
|
|
49
|
+
* The first fault found, in this order: not an ordinary record; `kind` missing, not text or outside the set; a member the
|
|
50
|
+
* kind does not have; a member it needs (for `env`, one of `headers` and `env`, written `headers|env`); a member of the
|
|
51
|
+
* wrong form. A `kind` or `field` read off the caller's value is untrusted text, cut to 64 characters with unsafe
|
|
52
|
+
* characters replaced; fence it before rendering it where markup is interpreted.
|
|
53
|
+
*/
|
|
54
|
+
export type CredentialRefProblem = {
|
|
55
|
+
readonly reason: "not_a_record";
|
|
56
|
+
} | {
|
|
57
|
+
readonly reason: "unknown_kind";
|
|
58
|
+
readonly kind: string;
|
|
59
|
+
} | {
|
|
60
|
+
readonly reason: "missing_field";
|
|
61
|
+
readonly kind?: CredentialRefKind;
|
|
62
|
+
readonly field: string;
|
|
63
|
+
} | {
|
|
64
|
+
readonly reason: "extra_field";
|
|
65
|
+
readonly kind: CredentialRefKind;
|
|
66
|
+
readonly field: string;
|
|
67
|
+
} | {
|
|
68
|
+
readonly reason: "bad_value";
|
|
69
|
+
readonly kind?: CredentialRefKind;
|
|
70
|
+
readonly field: string;
|
|
71
|
+
};
|
|
72
|
+
export type CredentialRefReading = {
|
|
73
|
+
readonly ok: true;
|
|
74
|
+
readonly ref: CredentialRef;
|
|
75
|
+
} | {
|
|
76
|
+
readonly ok: false;
|
|
77
|
+
readonly problem: CredentialRefProblem;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* The one reader of a credential reference: a fresh frozen copy that the caller's later writes cannot reach (forward the
|
|
81
|
+
* copy, never the value read), or the first problem. Nothing inside it throws: a value whose members cannot be read (a
|
|
82
|
+
* revoked or throwing Proxy) is `not_a_record`, or the `bad_value` of the map that holds it. A getter is refused and never
|
|
83
|
+
* called, no prototype is read for a member, and a Proxy's own traps (on the value, its maps or its prototype) are the
|
|
84
|
+
* caller's code and run as for any property read. Text is compared as written: nothing is normalised, and visually
|
|
85
|
+
* confusable text is not judged. With no stack left a valid value may be refused (any reason), never an invalid one
|
|
86
|
+
* accepted, and the call itself may throw a `RangeError` before it is entered.
|
|
87
|
+
*/
|
|
88
|
+
export declare function readCredentialRef(value: unknown): CredentialRefReading;
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export const CREDENTIAL_REF_KINDS = Object.freeze(["env", "vault", "handle", "assertion"]);
|
|
2
|
+
export const CREDENTIAL_REF_TRUST = trustTable();
|
|
3
|
+
function trustTable() {
|
|
4
|
+
const rows = Object.create(null);
|
|
5
|
+
rows.env = Object.freeze({ request: "refused" });
|
|
6
|
+
rows.vault = Object.freeze({ request: "own_entry" });
|
|
7
|
+
rows.handle = Object.freeze({ request: "refused" });
|
|
8
|
+
rows.assertion = Object.freeze({ request: "refused" });
|
|
9
|
+
return Object.freeze(rows);
|
|
10
|
+
}
|
|
11
|
+
export const CREDENTIAL_REF_TEXT_MAX = 2048;
|
|
12
|
+
export const CREDENTIAL_REF_MAP_MAX = 64;
|
|
13
|
+
export const CREDENTIAL_REF_PROBLEM_REASONS = Object.freeze(["not_a_record", "unknown_kind", "missing_field", "extra_field", "bad_value"]);
|
|
14
|
+
const MEMBERS = {
|
|
15
|
+
env: ["headers", "env"],
|
|
16
|
+
vault: ["entry"],
|
|
17
|
+
handle: ["id"],
|
|
18
|
+
assertion: ["audience"],
|
|
19
|
+
};
|
|
20
|
+
const TEXT_MEMBER = { vault: "entry", handle: "id", assertion: "audience" };
|
|
21
|
+
const HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
22
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
23
|
+
const ECHO_MAX = 64;
|
|
24
|
+
const UNSAFE = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\p{DI}\p{NChar}\p{Co}\ud800-\udfff]/u;
|
|
25
|
+
const UNSAFE_ALL = new RegExp(UNSAFE.source, "gu");
|
|
26
|
+
const ABSENT = Object.freeze({ found: "absent" });
|
|
27
|
+
const ACCESSOR = Object.freeze({ found: "accessor" });
|
|
28
|
+
const NOT_A_RECORD = Object.freeze({ ok: false, problem: Object.freeze({ reason: "not_a_record" }) });
|
|
29
|
+
function isRecord(value) {
|
|
30
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
31
|
+
return false;
|
|
32
|
+
const proto = Object.getPrototypeOf(value);
|
|
33
|
+
return proto === null || Object.getPrototypeOf(proto) === null;
|
|
34
|
+
}
|
|
35
|
+
function member(record, key) {
|
|
36
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, key);
|
|
37
|
+
if (descriptor === undefined || descriptor.enumerable !== true)
|
|
38
|
+
return ABSENT;
|
|
39
|
+
return Object.hasOwn(descriptor, "value") ? { found: "value", value: descriptor.value } : ACCESSOR;
|
|
40
|
+
}
|
|
41
|
+
const echo = (text) => text.slice(0, ECHO_MAX).replace(UNSAFE_ALL, "?");
|
|
42
|
+
const refused = (problem) => Object.freeze({ ok: false, problem: Object.freeze(problem) });
|
|
43
|
+
const accepted = (ref) => Object.freeze({ ok: true, ref });
|
|
44
|
+
const isText = (value) => typeof value === "string" && value.length > 0 && value.length <= CREDENTIAL_REF_TEXT_MAX && !UNSAFE.test(value) && value === value.trim();
|
|
45
|
+
const isName = (value, form) => value.length <= CREDENTIAL_REF_TEXT_MAX && form.test(value);
|
|
46
|
+
function nameMap(value, keyForm, foldCase) {
|
|
47
|
+
try {
|
|
48
|
+
if (!isRecord(value))
|
|
49
|
+
return undefined;
|
|
50
|
+
const names = Object.keys(value);
|
|
51
|
+
if (names.length === 0 || names.length > CREDENTIAL_REF_MAP_MAX)
|
|
52
|
+
return undefined;
|
|
53
|
+
const out = Object.create(null);
|
|
54
|
+
const folded = new Set();
|
|
55
|
+
for (const name of names) {
|
|
56
|
+
const found = member(value, name);
|
|
57
|
+
if (found.found !== "value" || typeof found.value !== "string")
|
|
58
|
+
return undefined;
|
|
59
|
+
if (!isName(name, keyForm) || !isName(found.value, ENV_NAME))
|
|
60
|
+
return undefined;
|
|
61
|
+
if (foldCase) {
|
|
62
|
+
const key = name.toLowerCase();
|
|
63
|
+
if (folded.has(key))
|
|
64
|
+
return undefined;
|
|
65
|
+
folded.add(key);
|
|
66
|
+
}
|
|
67
|
+
out[name] = found.value;
|
|
68
|
+
}
|
|
69
|
+
return Object.freeze(out);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const mapOf = (found, keyForm, foldCase) => (found.found === "value" ? nameMap(found.value, keyForm, foldCase) : undefined);
|
|
76
|
+
function envRef(record) {
|
|
77
|
+
const given = { headers: member(record, "headers"), env: member(record, "env") };
|
|
78
|
+
if (given.headers.found === "absent" && given.env.found === "absent")
|
|
79
|
+
return refused({ reason: "missing_field", kind: "env", field: "headers|env" });
|
|
80
|
+
const headers = mapOf(given.headers, HEADER_NAME, true);
|
|
81
|
+
if (given.headers.found !== "absent" && headers === undefined)
|
|
82
|
+
return refused({ reason: "bad_value", kind: "env", field: "headers" });
|
|
83
|
+
const env = mapOf(given.env, ENV_NAME, false);
|
|
84
|
+
if (given.env.found !== "absent" && env === undefined)
|
|
85
|
+
return refused({ reason: "bad_value", kind: "env", field: "env" });
|
|
86
|
+
return accepted(Object.freeze({ kind: "env", ...(headers === undefined ? {} : { headers }), ...(env === undefined ? {} : { env }) }));
|
|
87
|
+
}
|
|
88
|
+
function read(value) {
|
|
89
|
+
if (!isRecord(value))
|
|
90
|
+
return NOT_A_RECORD;
|
|
91
|
+
const named = member(value, "kind");
|
|
92
|
+
if (named.found === "absent")
|
|
93
|
+
return refused({ reason: "missing_field", field: "kind" });
|
|
94
|
+
if (named.found !== "value" || typeof named.value !== "string")
|
|
95
|
+
return refused({ reason: "bad_value", field: "kind" });
|
|
96
|
+
const text = named.value;
|
|
97
|
+
const kind = CREDENTIAL_REF_KINDS.find((k) => k === text);
|
|
98
|
+
if (kind === undefined)
|
|
99
|
+
return refused({ reason: "unknown_kind", kind: echo(text) });
|
|
100
|
+
for (const key of Object.keys(value)) {
|
|
101
|
+
if (key !== "kind" && !MEMBERS[kind].includes(key))
|
|
102
|
+
return refused({ reason: "extra_field", kind, field: echo(key) });
|
|
103
|
+
}
|
|
104
|
+
if (kind === "env")
|
|
105
|
+
return envRef(value);
|
|
106
|
+
const field = TEXT_MEMBER[kind];
|
|
107
|
+
const found = member(value, field);
|
|
108
|
+
if (found.found === "absent")
|
|
109
|
+
return refused({ reason: "missing_field", kind, field });
|
|
110
|
+
if (found.found !== "value" || !isText(found.value))
|
|
111
|
+
return refused({ reason: "bad_value", kind, field });
|
|
112
|
+
if (kind === "vault")
|
|
113
|
+
return accepted(Object.freeze({ kind, entry: found.value }));
|
|
114
|
+
if (kind === "handle")
|
|
115
|
+
return accepted(Object.freeze({ kind, id: found.value }));
|
|
116
|
+
return accepted(Object.freeze({ kind, audience: found.value }));
|
|
117
|
+
}
|
|
118
|
+
export function readCredentialRef(value) {
|
|
119
|
+
try {
|
|
120
|
+
return read(value);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return NOT_A_RECORD;
|
|
124
|
+
}
|
|
125
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** The package entry of `@sema-agent/primitives`; core imports the package by name, never these modules. */
|
|
2
|
+
export { CREDENTIAL_REF_KINDS, CREDENTIAL_REF_TRUST, CREDENTIAL_REF_TEXT_MAX, CREDENTIAL_REF_MAP_MAX, CREDENTIAL_REF_PROBLEM_REASONS, readCredentialRef, type CredentialRef, type CredentialRefKind, type CredentialRefRequestAdmission, type CredentialRefProblem, type CredentialRefProblemReason, type CredentialRefReading, } from "./credential-ref.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CREDENTIAL_REF_KINDS, CREDENTIAL_REF_TRUST, CREDENTIAL_REF_TEXT_MAX, CREDENTIAL_REF_MAP_MAX, CREDENTIAL_REF_PROBLEM_REASONS, readCredentialRef, } from "./credential-ref.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/primitives",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "7.58.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Shared low-level definitions of the sema engine, importable by any layer: the credential reference (closed set of kinds, one reader). Zero dependencies.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "BUSL-1.1",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/sema-agent/sema-core.git",
|
|
11
|
+
"directory": "packages/primitives"
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20.18.1"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.json --emitDeclarationOnly --removeComments false"
|
|
33
|
+
},
|
|
34
|
+
"gitHead": "05f1a14710d078da0b338fe8712e16f8d790f6bd"
|
|
35
|
+
}
|