@descryy/runtime-environment-profile 0.0.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/dist/credential-store.d.ts +71 -0
- package/dist/credential-store.d.ts.map +1 -0
- package/dist/credential-store.js +129 -0
- package/dist/credential-store.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/profile.d.ts +98 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +96 -0
- package/dist/profile.js.map +1 -0
- package/dist/safety-gate.d.ts +38 -0
- package/dist/safety-gate.d.ts.map +1 -0
- package/dist/safety-gate.js +34 -0
- package/dist/safety-gate.js.map +1 -0
- package/package.json +29 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage — DEC-270: "Credentials stored locally, in the OS
|
|
3
|
+
* keychain on the user's machine — not on a Descry server." This repo has
|
|
4
|
+
* no server component (`descry-core` CLAUDE.md, "Repo split"), so that
|
|
5
|
+
* guarantee is entirely a local-storage concern here.
|
|
6
|
+
*
|
|
7
|
+
* `CredentialStore` is the seam a real OS-keychain backend (`keytar` or
|
|
8
|
+
* equivalent) plugs into later. **Chosen for this pass:
|
|
9
|
+
* `EncryptedFileCredentialStore`, an at-rest AES-256-GCM file store, not a
|
|
10
|
+
* real keychain integration** — disclosed per the "honest degradation"
|
|
11
|
+
* rule (`descry-core` CLAUDE.md rule 7), not silently passed off as the
|
|
12
|
+
* real thing:
|
|
13
|
+
*
|
|
14
|
+
* - Neither this repo's root `package.json` nor `descry-core`'s already
|
|
15
|
+
* depends on `keytar` or any keychain wrapper (checked before writing
|
|
16
|
+
* this file), and `keytar` itself is an unmaintained native module
|
|
17
|
+
* (prebuilt binaries, no upstream releases since 2021) — exactly the
|
|
18
|
+
* "heavy new dependency added without checking existing ones" this
|
|
19
|
+
* pass was told to avoid, for a package that would need per-platform
|
|
20
|
+
* native builds to even test in CI.
|
|
21
|
+
* - `node:crypto` is already a dependency of this repo (`randomUUID` is
|
|
22
|
+
* used in `backend-observation`, `browser`, `controller`,
|
|
23
|
+
* `evidence-store`) and needs no native build step, so the interim
|
|
24
|
+
* implementation is buildable and testable everywhere `node --test`
|
|
25
|
+
* already runs.
|
|
26
|
+
* - The interface below does not leak this choice: a future
|
|
27
|
+
* `KeychainCredentialStore` satisfies the same three methods and every
|
|
28
|
+
* caller (including the safety gate) is unaffected by the swap.
|
|
29
|
+
*/
|
|
30
|
+
export interface CredentialStore {
|
|
31
|
+
/** Stores `secret` under `credentialRef`, overwriting any existing value for that ref. */
|
|
32
|
+
store(credentialRef: string, secret: string): Promise<void>;
|
|
33
|
+
/** Returns the stored secret, or `null` if nothing is stored under this exact `credentialRef`. */
|
|
34
|
+
retrieve(credentialRef: string): Promise<string | null>;
|
|
35
|
+
/** Removes any stored secret for `credentialRef`. A no-op, not an error, if nothing was stored. */
|
|
36
|
+
delete(credentialRef: string): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* AES-256-GCM file-at-rest fallback. One file per `credentialRef`, named
|
|
40
|
+
* by the SHA-256 hash of the ref rather than the ref itself — so scoping
|
|
41
|
+
* is enforced structurally (a lookup under a different ref hashes to a
|
|
42
|
+
* different filename and simply finds nothing) rather than only by
|
|
43
|
+
* convention, and so a ref containing characters unsafe for a filename
|
|
44
|
+
* never needs special-casing.
|
|
45
|
+
*
|
|
46
|
+
* The AES key is a 32-byte value generated once and written to
|
|
47
|
+
* `master.key` inside the store directory, `chmod`'d to `0o600`
|
|
48
|
+
* (owner-only) immediately after write — the directory itself is created
|
|
49
|
+
* `0o700`. This protects against other *users* on a shared machine, not
|
|
50
|
+
* against another *process running as the same user*, which is the
|
|
51
|
+
* limitation a real OS keychain (backed by the platform's own access
|
|
52
|
+
* control / user login secret) does not have. That gap is exactly why
|
|
53
|
+
* this is documented as an interim fallback rather than the intended
|
|
54
|
+
* long-term implementation.
|
|
55
|
+
*/
|
|
56
|
+
export declare class EncryptedFileCredentialStore implements CredentialStore {
|
|
57
|
+
private readonly baseDir;
|
|
58
|
+
private readonly entriesDir;
|
|
59
|
+
private readonly keyPath;
|
|
60
|
+
private keyPromise;
|
|
61
|
+
constructor(options?: {
|
|
62
|
+
readonly baseDir?: string;
|
|
63
|
+
});
|
|
64
|
+
store(credentialRef: string, secret: string): Promise<void>;
|
|
65
|
+
retrieve(credentialRef: string): Promise<string | null>;
|
|
66
|
+
delete(credentialRef: string): Promise<void>;
|
|
67
|
+
private entryPath;
|
|
68
|
+
private loadOrCreateKey;
|
|
69
|
+
private loadOrCreateKeyUncached;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=credential-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-store.d.ts","sourceRoot":"","sources":["../src/credential-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAQH,MAAM,WAAW,eAAe;IAC9B,0FAA0F;IAC1F,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,kGAAkG;IAClG,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,mGAAmG;IACnG,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAYD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,4BAA6B,YAAW,eAAe;IAClE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,UAAU,CAAgC;gBAEtC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAM7C,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB3D,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAiBvD,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlD,OAAO,CAAC,SAAS;YAKH,eAAe;YAOf,uBAAuB;CAUtC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage — DEC-270: "Credentials stored locally, in the OS
|
|
3
|
+
* keychain on the user's machine — not on a Descry server." This repo has
|
|
4
|
+
* no server component (`descry-core` CLAUDE.md, "Repo split"), so that
|
|
5
|
+
* guarantee is entirely a local-storage concern here.
|
|
6
|
+
*
|
|
7
|
+
* `CredentialStore` is the seam a real OS-keychain backend (`keytar` or
|
|
8
|
+
* equivalent) plugs into later. **Chosen for this pass:
|
|
9
|
+
* `EncryptedFileCredentialStore`, an at-rest AES-256-GCM file store, not a
|
|
10
|
+
* real keychain integration** — disclosed per the "honest degradation"
|
|
11
|
+
* rule (`descry-core` CLAUDE.md rule 7), not silently passed off as the
|
|
12
|
+
* real thing:
|
|
13
|
+
*
|
|
14
|
+
* - Neither this repo's root `package.json` nor `descry-core`'s already
|
|
15
|
+
* depends on `keytar` or any keychain wrapper (checked before writing
|
|
16
|
+
* this file), and `keytar` itself is an unmaintained native module
|
|
17
|
+
* (prebuilt binaries, no upstream releases since 2021) — exactly the
|
|
18
|
+
* "heavy new dependency added without checking existing ones" this
|
|
19
|
+
* pass was told to avoid, for a package that would need per-platform
|
|
20
|
+
* native builds to even test in CI.
|
|
21
|
+
* - `node:crypto` is already a dependency of this repo (`randomUUID` is
|
|
22
|
+
* used in `backend-observation`, `browser`, `controller`,
|
|
23
|
+
* `evidence-store`) and needs no native build step, so the interim
|
|
24
|
+
* implementation is buildable and testable everywhere `node --test`
|
|
25
|
+
* already runs.
|
|
26
|
+
* - The interface below does not leak this choice: a future
|
|
27
|
+
* `KeychainCredentialStore` satisfies the same three methods and every
|
|
28
|
+
* caller (including the safety gate) is unaffected by the swap.
|
|
29
|
+
*/
|
|
30
|
+
import { randomBytes, createCipheriv, createDecipheriv, createHash } from "node:crypto";
|
|
31
|
+
import { mkdir, readFile, writeFile, unlink, chmod } from "node:fs/promises";
|
|
32
|
+
import { existsSync } from "node:fs";
|
|
33
|
+
import { homedir } from "node:os";
|
|
34
|
+
import { join } from "node:path";
|
|
35
|
+
const ALGORITHM = "aes-256-gcm";
|
|
36
|
+
const KEY_BYTES = 32;
|
|
37
|
+
const IV_BYTES = 12;
|
|
38
|
+
/**
|
|
39
|
+
* AES-256-GCM file-at-rest fallback. One file per `credentialRef`, named
|
|
40
|
+
* by the SHA-256 hash of the ref rather than the ref itself — so scoping
|
|
41
|
+
* is enforced structurally (a lookup under a different ref hashes to a
|
|
42
|
+
* different filename and simply finds nothing) rather than only by
|
|
43
|
+
* convention, and so a ref containing characters unsafe for a filename
|
|
44
|
+
* never needs special-casing.
|
|
45
|
+
*
|
|
46
|
+
* The AES key is a 32-byte value generated once and written to
|
|
47
|
+
* `master.key` inside the store directory, `chmod`'d to `0o600`
|
|
48
|
+
* (owner-only) immediately after write — the directory itself is created
|
|
49
|
+
* `0o700`. This protects against other *users* on a shared machine, not
|
|
50
|
+
* against another *process running as the same user*, which is the
|
|
51
|
+
* limitation a real OS keychain (backed by the platform's own access
|
|
52
|
+
* control / user login secret) does not have. That gap is exactly why
|
|
53
|
+
* this is documented as an interim fallback rather than the intended
|
|
54
|
+
* long-term implementation.
|
|
55
|
+
*/
|
|
56
|
+
export class EncryptedFileCredentialStore {
|
|
57
|
+
baseDir;
|
|
58
|
+
entriesDir;
|
|
59
|
+
keyPath;
|
|
60
|
+
keyPromise = null;
|
|
61
|
+
constructor(options) {
|
|
62
|
+
this.baseDir = options?.baseDir ?? join(homedir(), ".descry", "credentials");
|
|
63
|
+
this.entriesDir = join(this.baseDir, "entries");
|
|
64
|
+
this.keyPath = join(this.baseDir, "master.key");
|
|
65
|
+
}
|
|
66
|
+
async store(credentialRef, secret) {
|
|
67
|
+
await mkdir(this.entriesDir, { recursive: true, mode: 0o700 });
|
|
68
|
+
const key = await this.loadOrCreateKey();
|
|
69
|
+
const iv = randomBytes(IV_BYTES);
|
|
70
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
71
|
+
const ciphertext = Buffer.concat([cipher.update(secret, "utf8"), cipher.final()]);
|
|
72
|
+
const envelope = {
|
|
73
|
+
iv: iv.toString("base64"),
|
|
74
|
+
authTag: cipher.getAuthTag().toString("base64"),
|
|
75
|
+
ciphertext: ciphertext.toString("base64"),
|
|
76
|
+
};
|
|
77
|
+
const path = this.entryPath(credentialRef);
|
|
78
|
+
await writeFile(path, JSON.stringify(envelope), { mode: 0o600 });
|
|
79
|
+
await chmod(path, 0o600);
|
|
80
|
+
}
|
|
81
|
+
async retrieve(credentialRef) {
|
|
82
|
+
const path = this.entryPath(credentialRef);
|
|
83
|
+
if (!existsSync(path)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const key = await this.loadOrCreateKey();
|
|
87
|
+
const raw = await readFile(path, "utf8");
|
|
88
|
+
const envelope = JSON.parse(raw);
|
|
89
|
+
const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(envelope.iv, "base64"));
|
|
90
|
+
decipher.setAuthTag(Buffer.from(envelope.authTag, "base64"));
|
|
91
|
+
const plaintext = Buffer.concat([
|
|
92
|
+
decipher.update(Buffer.from(envelope.ciphertext, "base64")),
|
|
93
|
+
decipher.final(),
|
|
94
|
+
]);
|
|
95
|
+
return plaintext.toString("utf8");
|
|
96
|
+
}
|
|
97
|
+
async delete(credentialRef) {
|
|
98
|
+
const path = this.entryPath(credentialRef);
|
|
99
|
+
try {
|
|
100
|
+
await unlink(path);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (error.code !== "ENOENT") {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
entryPath(credentialRef) {
|
|
109
|
+
const digest = createHash("sha256").update(credentialRef, "utf8").digest("hex");
|
|
110
|
+
return join(this.entriesDir, `${digest}.json`);
|
|
111
|
+
}
|
|
112
|
+
async loadOrCreateKey() {
|
|
113
|
+
if (this.keyPromise === null) {
|
|
114
|
+
this.keyPromise = this.loadOrCreateKeyUncached();
|
|
115
|
+
}
|
|
116
|
+
return this.keyPromise;
|
|
117
|
+
}
|
|
118
|
+
async loadOrCreateKeyUncached() {
|
|
119
|
+
await mkdir(this.baseDir, { recursive: true, mode: 0o700 });
|
|
120
|
+
if (existsSync(this.keyPath)) {
|
|
121
|
+
return readFile(this.keyPath);
|
|
122
|
+
}
|
|
123
|
+
const key = randomBytes(KEY_BYTES);
|
|
124
|
+
await writeFile(this.keyPath, key, { mode: 0o600 });
|
|
125
|
+
await chmod(this.keyPath, 0o600);
|
|
126
|
+
return key;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=credential-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../src/credential-store.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { EnvironmentProfile, SafetyLevel, ProfileValidationError, ProfileMode } from "./profile.ts";
|
|
2
|
+
export { SAFETY_LEVELS, PROFILE_MODES, validateProfile, createEnvironmentProfile } from "./profile.ts";
|
|
3
|
+
export type { CredentialStore } from "./credential-store.ts";
|
|
4
|
+
export { EncryptedFileCredentialStore } from "./credential-store.ts";
|
|
5
|
+
export type { GateDecision, ProposedAction } from "./safety-gate.ts";
|
|
6
|
+
export { GATE_DECISIONS, evaluateAction } from "./safety-gate.ts";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAEvG,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAErE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { SAFETY_LEVELS, PROFILE_MODES, validateProfile, createEnvironmentProfile } from "./profile.js";
|
|
2
|
+
export { EncryptedFileCredentialStore } from "./credential-store.js";
|
|
3
|
+
export { GATE_DECISIONS, evaluateAction } from "./safety-gate.js";
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAGvG,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAGrE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment profile — DEC-270 (`documents/decisions-inbox/
|
|
3
|
+
* DEC-270-environment-profiles-declare-safety-not-infer-it.md`), building on
|
|
4
|
+
* DEC-266's four target kinds (local booted, local attached, remote
|
|
5
|
+
* dev/staging/preview, production).
|
|
6
|
+
*
|
|
7
|
+
* A profile is a **user-named** target: no fixed vocabulary. "staging"
|
|
8
|
+
* means different things at different companies, so nothing here
|
|
9
|
+
* pattern-matches on `name` — the safety level below is the only thing
|
|
10
|
+
* that governs what is permitted, and it is always set explicitly by the
|
|
11
|
+
* user, never guessed from the name or the URL.
|
|
12
|
+
*
|
|
13
|
+
* `credentialRef` is a reference into a `CredentialStore` (see
|
|
14
|
+
* `credential-store.ts`), never the raw secret — this type is safe to log,
|
|
15
|
+
* serialize or hand to a planner without ever exposing what it points at.
|
|
16
|
+
*
|
|
17
|
+
* `mode` and `remoteLogSource` are RT-197/DEC-271's addition. Until this
|
|
18
|
+
* pass `EnvironmentProfile` had no field naming which of DEC-266's four
|
|
19
|
+
* kinds a given profile actually was — `@descryhq-wq/runtime-remote-log-ingestion`
|
|
20
|
+
* existed with real tests and zero callers, and there was no profile-level
|
|
21
|
+
* place for a "remote/production" target to declare where its logs live,
|
|
22
|
+
* so nothing could have called it. `mode` closes that: it is what
|
|
23
|
+
* `packages/orchestrator/src/profile-backend-observation.ts` reads to
|
|
24
|
+
* choose DEC-271's mechanism — a real `InboundProxy` for `"localBooted"`,
|
|
25
|
+
* `remote-log-ingestion` for `"remote"`/`"production"`, never both.
|
|
26
|
+
*/
|
|
27
|
+
import type { RemoteLogSourceConfig } from "@descryhq-wq/runtime-remote-log-ingestion";
|
|
28
|
+
/** Set once by the user per profile. Never inferred from `name` (DEC-270's core rule). */
|
|
29
|
+
export declare const SAFETY_LEVELS: readonly ["readOnly", "write", "destructiveWithApproval"];
|
|
30
|
+
export type SafetyLevel = (typeof SAFETY_LEVELS)[number];
|
|
31
|
+
/**
|
|
32
|
+
* DEC-266's four target kinds, named as a closed union for the first time
|
|
33
|
+
* (RT-197). Set once by the user per profile, same discipline as
|
|
34
|
+
* `safetyLevel` — never inferred from `name` or `url`.
|
|
35
|
+
*/
|
|
36
|
+
export declare const PROFILE_MODES: readonly ["localBooted", "localAttached", "remote", "production"];
|
|
37
|
+
export type ProfileMode = (typeof PROFILE_MODES)[number];
|
|
38
|
+
export interface EnvironmentProfile {
|
|
39
|
+
/** User-given, free-form. Not matched against any fixed vocabulary anywhere in this package. */
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly url: string;
|
|
42
|
+
readonly safetyLevel: SafetyLevel;
|
|
43
|
+
/**
|
|
44
|
+
* Opaque key into a `CredentialStore` — never the raw secret itself.
|
|
45
|
+
* Scoped to this profile only: two profiles must never share a
|
|
46
|
+
* `credentialRef`, so a credential entered for one is never retrievable
|
|
47
|
+
* under another's identity, even by accident (DEC-270).
|
|
48
|
+
*/
|
|
49
|
+
readonly credentialRef: string;
|
|
50
|
+
/** Which of DEC-266's four kinds this profile targets. Governs DEC-271's mechanism choice — see this file's own header. */
|
|
51
|
+
readonly mode: ProfileMode;
|
|
52
|
+
/**
|
|
53
|
+
* DEC-271's "ask, don't guess" input for the remote/production mechanism
|
|
54
|
+
* — where the user says their logs live, per
|
|
55
|
+
* `@descryhq-wq/runtime-remote-log-ingestion`'s `RemoteLogSourceConfig`. Only
|
|
56
|
+
* meaningful for `mode: "remote"` or `mode: "production"`;
|
|
57
|
+
* `validateProfile` rejects it on a `"localBooted"`/`"localAttached"`
|
|
58
|
+
* profile rather than silently ignoring it, because a caller that set it
|
|
59
|
+
* there believed it would be honored and it would not have been.
|
|
60
|
+
*
|
|
61
|
+
* `null`/absent on a remote or production profile is a real, honest
|
|
62
|
+
* state — DEC-271's own "or a plain 'not observable here' when there's
|
|
63
|
+
* nothing to work with" — not an error. This type has no dependency in
|
|
64
|
+
* the other direction: `@descryhq-wq/runtime-remote-log-ingestion` does not
|
|
65
|
+
* import from this package.
|
|
66
|
+
*/
|
|
67
|
+
readonly remoteLogSource?: RemoteLogSourceConfig | null;
|
|
68
|
+
}
|
|
69
|
+
export type ProfileValidationError = "EMPTY_NAME" | "EMPTY_CREDENTIAL_REF" | "INVALID_URL" | "INVALID_SAFETY_LEVEL" | "INVALID_MODE" | "REMOTE_LOG_SOURCE_NOT_ALLOWED_FOR_MODE";
|
|
70
|
+
/**
|
|
71
|
+
* Structural validation only — no attempt to reach `url` or verify the
|
|
72
|
+
* credential exists. Reachability and secret presence are runtime
|
|
73
|
+
* concerns; this stays a pure check so it can run before either is
|
|
74
|
+
* possible (e.g. while a setup form is still being filled in).
|
|
75
|
+
*/
|
|
76
|
+
export declare function validateProfile(input: {
|
|
77
|
+
readonly name: string;
|
|
78
|
+
readonly url: string;
|
|
79
|
+
readonly safetyLevel: string;
|
|
80
|
+
readonly credentialRef: string;
|
|
81
|
+
readonly mode: string;
|
|
82
|
+
readonly remoteLogSource?: RemoteLogSourceConfig | null;
|
|
83
|
+
}): readonly ProfileValidationError[];
|
|
84
|
+
/**
|
|
85
|
+
* Validates and constructs a profile in one step. Throws with every
|
|
86
|
+
* validation error joined, rather than the first one only, so a caller
|
|
87
|
+
* building a setup form doesn't have to fix and resubmit one field at a
|
|
88
|
+
* time.
|
|
89
|
+
*/
|
|
90
|
+
export declare function createEnvironmentProfile(input: {
|
|
91
|
+
readonly name: string;
|
|
92
|
+
readonly url: string;
|
|
93
|
+
readonly safetyLevel: SafetyLevel;
|
|
94
|
+
readonly credentialRef: string;
|
|
95
|
+
readonly mode: ProfileMode;
|
|
96
|
+
readonly remoteLogSource?: RemoteLogSourceConfig | null;
|
|
97
|
+
}): EnvironmentProfile;
|
|
98
|
+
//# sourceMappingURL=profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAEvF,0FAA0F;AAC1F,eAAO,MAAM,aAAa,2DAA4D,CAAC;AACvF,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD;;;;GAIG;AACH,eAAO,MAAM,aAAa,mEAAoE,CAAC;AAC/F,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IACjC,gGAAgG;IAChG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,2HAA2H;IAC3H,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACzD;AAED,MAAM,MAAM,sBAAsB,GAC9B,YAAY,GACZ,sBAAsB,GACtB,aAAa,GACb,sBAAsB,GACtB,cAAc,GACd,wCAAwC,CAAC;AAE7C;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,eAAe,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACzD,GAAG,SAAS,sBAAsB,EAAE,CA6BpC;AAWD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,eAAe,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACzD,GAAG,kBAAkB,CAarB"}
|
package/dist/profile.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment profile — DEC-270 (`documents/decisions-inbox/
|
|
3
|
+
* DEC-270-environment-profiles-declare-safety-not-infer-it.md`), building on
|
|
4
|
+
* DEC-266's four target kinds (local booted, local attached, remote
|
|
5
|
+
* dev/staging/preview, production).
|
|
6
|
+
*
|
|
7
|
+
* A profile is a **user-named** target: no fixed vocabulary. "staging"
|
|
8
|
+
* means different things at different companies, so nothing here
|
|
9
|
+
* pattern-matches on `name` — the safety level below is the only thing
|
|
10
|
+
* that governs what is permitted, and it is always set explicitly by the
|
|
11
|
+
* user, never guessed from the name or the URL.
|
|
12
|
+
*
|
|
13
|
+
* `credentialRef` is a reference into a `CredentialStore` (see
|
|
14
|
+
* `credential-store.ts`), never the raw secret — this type is safe to log,
|
|
15
|
+
* serialize or hand to a planner without ever exposing what it points at.
|
|
16
|
+
*
|
|
17
|
+
* `mode` and `remoteLogSource` are RT-197/DEC-271's addition. Until this
|
|
18
|
+
* pass `EnvironmentProfile` had no field naming which of DEC-266's four
|
|
19
|
+
* kinds a given profile actually was — `@descryhq-wq/runtime-remote-log-ingestion`
|
|
20
|
+
* existed with real tests and zero callers, and there was no profile-level
|
|
21
|
+
* place for a "remote/production" target to declare where its logs live,
|
|
22
|
+
* so nothing could have called it. `mode` closes that: it is what
|
|
23
|
+
* `packages/orchestrator/src/profile-backend-observation.ts` reads to
|
|
24
|
+
* choose DEC-271's mechanism — a real `InboundProxy` for `"localBooted"`,
|
|
25
|
+
* `remote-log-ingestion` for `"remote"`/`"production"`, never both.
|
|
26
|
+
*/
|
|
27
|
+
/** Set once by the user per profile. Never inferred from `name` (DEC-270's core rule). */
|
|
28
|
+
export const SAFETY_LEVELS = ["readOnly", "write", "destructiveWithApproval"];
|
|
29
|
+
/**
|
|
30
|
+
* DEC-266's four target kinds, named as a closed union for the first time
|
|
31
|
+
* (RT-197). Set once by the user per profile, same discipline as
|
|
32
|
+
* `safetyLevel` — never inferred from `name` or `url`.
|
|
33
|
+
*/
|
|
34
|
+
export const PROFILE_MODES = ["localBooted", "localAttached", "remote", "production"];
|
|
35
|
+
/**
|
|
36
|
+
* Structural validation only — no attempt to reach `url` or verify the
|
|
37
|
+
* credential exists. Reachability and secret presence are runtime
|
|
38
|
+
* concerns; this stays a pure check so it can run before either is
|
|
39
|
+
* possible (e.g. while a setup form is still being filled in).
|
|
40
|
+
*/
|
|
41
|
+
export function validateProfile(input) {
|
|
42
|
+
const errors = [];
|
|
43
|
+
if (input.name.trim().length === 0) {
|
|
44
|
+
errors.push("EMPTY_NAME");
|
|
45
|
+
}
|
|
46
|
+
if (input.credentialRef.trim().length === 0) {
|
|
47
|
+
errors.push("EMPTY_CREDENTIAL_REF");
|
|
48
|
+
}
|
|
49
|
+
if (!isParsableUrl(input.url)) {
|
|
50
|
+
errors.push("INVALID_URL");
|
|
51
|
+
}
|
|
52
|
+
if (!SAFETY_LEVELS.includes(input.safetyLevel)) {
|
|
53
|
+
errors.push("INVALID_SAFETY_LEVEL");
|
|
54
|
+
}
|
|
55
|
+
if (!PROFILE_MODES.includes(input.mode)) {
|
|
56
|
+
errors.push("INVALID_MODE");
|
|
57
|
+
}
|
|
58
|
+
// A log source declared on a mode DEC-271 never reads it for is a
|
|
59
|
+
// silently-ignored setting, not a harmless extra -- rejected rather than
|
|
60
|
+
// dropped, so the caller who set it learns it would not have been honored.
|
|
61
|
+
if (input.remoteLogSource != null &&
|
|
62
|
+
(input.mode === "localBooted" || input.mode === "localAttached")) {
|
|
63
|
+
errors.push("REMOTE_LOG_SOURCE_NOT_ALLOWED_FOR_MODE");
|
|
64
|
+
}
|
|
65
|
+
return errors;
|
|
66
|
+
}
|
|
67
|
+
function isParsableUrl(url) {
|
|
68
|
+
try {
|
|
69
|
+
void new URL(url);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Validates and constructs a profile in one step. Throws with every
|
|
78
|
+
* validation error joined, rather than the first one only, so a caller
|
|
79
|
+
* building a setup form doesn't have to fix and resubmit one field at a
|
|
80
|
+
* time.
|
|
81
|
+
*/
|
|
82
|
+
export function createEnvironmentProfile(input) {
|
|
83
|
+
const errors = validateProfile(input);
|
|
84
|
+
if (errors.length > 0) {
|
|
85
|
+
throw new Error(`Invalid environment profile: ${errors.join(", ")}`);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
name: input.name.trim(),
|
|
89
|
+
url: input.url,
|
|
90
|
+
safetyLevel: input.safetyLevel,
|
|
91
|
+
credentialRef: input.credentialRef.trim(),
|
|
92
|
+
mode: input.mode,
|
|
93
|
+
remoteLogSource: input.remoteLogSource ?? null,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,0FAA0F;AAC1F,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,yBAAyB,CAAU,CAAC;AAGvF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,YAAY,CAAU,CAAC;AA2C/F;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAO/B;IACC,MAAM,MAAM,GAA6B,EAAE,CAAC;IAE5C,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5B,CAAC;IACD,IAAI,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5C,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,CAAE,aAAmC,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QACtE,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,CAAE,aAAmC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/D,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC9B,CAAC;IACD,kEAAkE;IAClE,yEAAyE;IACzE,2EAA2E;IAC3E,IACE,KAAK,CAAC,eAAe,IAAI,IAAI;QAC7B,CAAC,KAAK,CAAC,IAAI,KAAK,aAAa,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,CAAC,EAChE,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IAChC,IAAI,CAAC;QACH,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAOxC;IACC,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,gCAAgC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE;QACvB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,aAAa,EAAE,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE;QACzC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,eAAe,EAAE,KAAK,CAAC,eAAe,IAAI,IAAI;KAC/C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety gate — the enforcement point DEC-270 requires: "Any destructive
|
|
3
|
+
* action checks the profile's declared level before running, not the
|
|
4
|
+
* environment's name or the fact that it's 'not prod.'"
|
|
5
|
+
*
|
|
6
|
+
* This function does not wire itself into any caller — DEC-270 only asks
|
|
7
|
+
* that it exist and be correct; the runners that actually perform
|
|
8
|
+
* destructive actions (a different lane's work) call it before acting.
|
|
9
|
+
*/
|
|
10
|
+
import type { EnvironmentProfile } from "./profile.ts";
|
|
11
|
+
export declare const GATE_DECISIONS: readonly ["allow", "deny", "requiresApproval"];
|
|
12
|
+
export type GateDecision = (typeof GATE_DECISIONS)[number];
|
|
13
|
+
export interface ProposedAction {
|
|
14
|
+
/** True for anything that changes state on the target — not just destructive actions. */
|
|
15
|
+
readonly write: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* True for an irreversible or high-blast-radius action (delete, drop,
|
|
18
|
+
* force-push, and similar). Every destructive action is implicitly a
|
|
19
|
+
* write too, whether or not `write` is also set explicitly.
|
|
20
|
+
*/
|
|
21
|
+
readonly destructive: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Decision matrix, one row per `SafetyLevel`:
|
|
25
|
+
*
|
|
26
|
+
* | safetyLevel | read-only action | non-destructive write | destructive |
|
|
27
|
+
* |----------------------------|-------------------|------------------------|-------------|
|
|
28
|
+
* | `readOnly` | allow | deny | deny |
|
|
29
|
+
* | `write` | allow | allow | deny |
|
|
30
|
+
* | `destructiveWithApproval` | allow | allow | requiresApproval |
|
|
31
|
+
*
|
|
32
|
+
* `write` denies destructive rather than escalating it to approval — a
|
|
33
|
+
* profile owner who chose `write` explicitly did not choose
|
|
34
|
+
* `destructiveWithApproval`, and DEC-270's whole point is that the level
|
|
35
|
+
* is declared, not inferred or auto-upgraded.
|
|
36
|
+
*/
|
|
37
|
+
export declare function evaluateAction(profile: EnvironmentProfile, action: ProposedAction): GateDecision;
|
|
38
|
+
//# sourceMappingURL=safety-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety-gate.d.ts","sourceRoot":"","sources":["../src/safety-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,cAAc,gDAAiD,CAAC;AAC7E,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC7B,yFAAyF;IACzF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,cAAc,GAAG,YAAY,CAQhG"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety gate — the enforcement point DEC-270 requires: "Any destructive
|
|
3
|
+
* action checks the profile's declared level before running, not the
|
|
4
|
+
* environment's name or the fact that it's 'not prod.'"
|
|
5
|
+
*
|
|
6
|
+
* This function does not wire itself into any caller — DEC-270 only asks
|
|
7
|
+
* that it exist and be correct; the runners that actually perform
|
|
8
|
+
* destructive actions (a different lane's work) call it before acting.
|
|
9
|
+
*/
|
|
10
|
+
export const GATE_DECISIONS = ["allow", "deny", "requiresApproval"];
|
|
11
|
+
/**
|
|
12
|
+
* Decision matrix, one row per `SafetyLevel`:
|
|
13
|
+
*
|
|
14
|
+
* | safetyLevel | read-only action | non-destructive write | destructive |
|
|
15
|
+
* |----------------------------|-------------------|------------------------|-------------|
|
|
16
|
+
* | `readOnly` | allow | deny | deny |
|
|
17
|
+
* | `write` | allow | allow | deny |
|
|
18
|
+
* | `destructiveWithApproval` | allow | allow | requiresApproval |
|
|
19
|
+
*
|
|
20
|
+
* `write` denies destructive rather than escalating it to approval — a
|
|
21
|
+
* profile owner who chose `write` explicitly did not choose
|
|
22
|
+
* `destructiveWithApproval`, and DEC-270's whole point is that the level
|
|
23
|
+
* is declared, not inferred or auto-upgraded.
|
|
24
|
+
*/
|
|
25
|
+
export function evaluateAction(profile, action) {
|
|
26
|
+
if (action.destructive) {
|
|
27
|
+
return profile.safetyLevel === "destructiveWithApproval" ? "requiresApproval" : "deny";
|
|
28
|
+
}
|
|
29
|
+
if (action.write) {
|
|
30
|
+
return profile.safetyLevel === "readOnly" ? "deny" : "allow";
|
|
31
|
+
}
|
|
32
|
+
return "allow";
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=safety-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety-gate.js","sourceRoot":"","sources":["../src/safety-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,kBAAkB,CAAU,CAAC;AAc7E;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAAC,OAA2B,EAAE,MAAsB;IAChF,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,OAAO,OAAO,CAAC,WAAW,KAAK,yBAAyB,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC;IACzF,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,OAAO,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAC/D,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@descryy/runtime-environment-profile",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Environment profiles per DEC-270/DEC-266: a user-named target (local booted/attached, remote dev/staging/preview, production) carrying a declared safety level, scoped credentials, and the safety-gate enforcement point destructive actions must call before running.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.5"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"registry": "https://registry.npmjs.org",
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -b"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@descryhq-wq/runtime-remote-log-ingestion": "0.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|