@elinpf/dsh-ops-access 0.1.7 → 0.2.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/README.md +9 -1
- package/README.zh.md +9 -1
- package/lib/backend.d.ts +113 -0
- package/lib/hub-backend.d.ts +83 -0
- package/lib/index.d.ts +44 -12
- package/lib/index.js +397 -294
- package/lib/types.d.ts +34 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,9 +7,11 @@ The ops access capability seam — owns the YAML credential registry (default `~
|
|
|
7
7
|
- **Single registry file, zero cache**: every `resolve`/`list`/`writeEntry` re-reads, re-parses, and re-validates the YAML — edits take effect immediately, no restart.
|
|
8
8
|
- **Tiered entries**: each profile carries an `ro` tier (agent-readable default) and an `rw` tier (served only through a registered broker grant).
|
|
9
9
|
- **Provider seam**: one provider per credential kind (`k8s`/`ceph`/`ssh` packages) supplies only a zod schema plus field processing (`~` expansion, content validation, capability probe). Providers register via `registerAccessProvider(ctx, provider)` — never hand-write `ctx.inject` for sibling services, it deadlocks the loader.
|
|
10
|
-
-
|
|
10
|
+
- **Reference fields** (`references`): a provider may declare that a field names another KIND's entry (ssh's `cred` → `ssh-cred`), so many entries share one credential instead of each carrying a copy. At resolve time core merges the referenced entry's fields UNDER the referring entry's (same registry, same tier, one level only); the broker is consulted once, on the referring entry. `validateResolved` is the post-merge hook for requirements that only hold on the merged shape (ssh needs a login user from either side). A dangling reference fails the referring resolve AND its `canResolve` precheck.
|
|
11
|
+
- **`register_access` tool**: the agent's self-service path for writing the ro tier; with `tier: "rw"` it instead **submits an rw registration request** (hub mode only, ADR-0008) — the request queues on the hub and is written only after an operator reviews the field contents and approves it in the admin settings section.
|
|
11
12
|
- **Mention support**: `@[kind/name](dsh-access:<payload>)` mentions are parsed on `agent/pre-step` and rewritten to readable references with envelope context; `GET /ops-access/list` feeds the browser's `@` picker. The encoding lives in the `./mention` subpath.
|
|
12
13
|
- **Admin routes**: `GET /ops-access/admin/list`, `GET /ops-access/admin/kinds`, `GET|POST|DELETE /ops-access/admin/entry` — envelope + validation status only, never field values.
|
|
14
|
+
- **Pluggable credential source** (`source`): `yaml` (default) is the local registry file; `hub` fetches entries from a standalone [ops-access-hub](../../ops-access-hub/) service on every call. In hub mode, file-field content is materialized to a SEPARATE cache dir (`hubCacheDir`, default `~/.dsh-ops/hub-cache`) as a TTL-bound cache (`materializeTtlMinutes`, default 15) — never a permanent copy: startup sweeps the whole cache (the grant ledger dies with the process, so cached rw material must not outlive it), the interval sweep expires by age, and resolve re-materializes transparently on demand. The yaml-mode `credentialsDir` (the documented fallback) is never swept.
|
|
13
15
|
|
|
14
16
|
## Design notes
|
|
15
17
|
|
|
@@ -25,6 +27,12 @@ The ops access capability seam — owns the YAML credential registry (default `~
|
|
|
25
27
|
name: '@elinpf/dsh-ops-access'
|
|
26
28
|
registryFile: ~/.dsh-ops/access.yaml # default
|
|
27
29
|
credentialsDir: ~/.dsh-ops/credentials # default; managed credential content files (0600)
|
|
30
|
+
# source: hub # optional; default yaml
|
|
31
|
+
# hubUrl: http://127.0.0.1:3090 # hub source: hub base URL
|
|
32
|
+
# hubToken: ... # hub source: read token (or env ACCESS_HUB_READ_TOKEN)
|
|
33
|
+
# hubAdminToken: ... # hub source: write token (or env ACCESS_HUB_ADMIN_TOKEN)
|
|
34
|
+
# hubCacheDir: ~/.dsh-ops/hub-cache # hub source: TTL materialization cache (never credentialsDir)
|
|
35
|
+
# materializeTtlMinutes: 15 # hub source: cache TTL; startup sweeps all
|
|
28
36
|
```
|
|
29
37
|
|
|
30
38
|
## Testing
|
package/README.zh.md
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
- **单注册表文件、零缓存**:每次 `resolve`/`list`/`writeEntry` 都重新读取、解析、校验 YAML — 改文件立即生效,无需重启。
|
|
8
8
|
- **分层条目**:每个 profile 携带 `ro` 层(agent 默认可读)和 `rw` 层(只有注册了 broker 授权后才发放)。
|
|
9
9
|
- **Provider 缝**:每种凭据类型一个 provider(`k8s`/`ceph`/`ssh` 包),只提供 zod schema 加字段处理(`~` 展开、内容校验、能力探测)。provider 通过 `registerAccessProvider(ctx, provider)` 注册 — 绝不要手写 `ctx.inject` 依赖兄弟服务,会死锁 loader。
|
|
10
|
-
-
|
|
10
|
+
- **引用字段**(`references`):provider 可声明某字段指向另一个种类的条目(ssh 的 `cred` → `ssh-cred`),让多个条目共享一份凭证而不是各自复制。resolve 时 core 把被引用条目的字段合并到引用方**之下**(同注册表、同 tier、只展开一层);broker 只被咨询一次,针对引用方条目。`validateResolved` 是合并后的校验钩子,承载只在合并形状上成立的要求(ssh 的登录用户可来自任一侧)。悬挂引用会让引用方的 resolve 及其 `canResolve` 预检一起失败。
|
|
11
|
+
- **`register_access` 工具**:agent 自助写入 ro 层的路径;传 `tier: "rw"` 则**提交 rw 注册申请**(仅 hub 模式,ADR-0008)——申请排队在 hub 上,管理员在凭证管理设置区审查字段内容并批准后才真正写入。
|
|
11
12
|
- **Mention 支持**:`@[kind/name](dsh-access:<payload>)` mention 在 `agent/pre-step` 上被解析、重写为可读引用并注入 envelope 上下文;`GET /ops-access/list` 给浏览器的 `@` 选择器供数。编码在 `./mention` 子路径。
|
|
12
13
|
- **Admin 路由**:`GET /ops-access/admin/list`、`GET /ops-access/admin/kinds`、`GET|POST|DELETE /ops-access/admin/entry` — 只出 envelope + 校验状态,绝不出字段值。
|
|
14
|
+
- **可插拔凭证来源**(`source`):`yaml`(默认)读本地注册表文件;`hub` 每次调用都从独立部署的 [ops-access-hub](../../ops-access-hub/) 服务拉取。hub 模式下,文件类字段的内容物化到**独立的缓存目录**(`hubCacheDir`,默认 `~/.dsh-ops/hub-cache`),是受 TTL 约束的缓存(`materializeTtlMinutes`,默认 15 分钟),**绝不是永久副本**:启动时全量清扫(grant 账本随进程消亡,缓存的 rw 材料不得比它活得久),定时清扫按年龄过期,resolve 现取现物化、到期透明重建。yaml 模式的 `credentialsDir`(文档化的回退路径)永不被清扫。
|
|
13
15
|
|
|
14
16
|
## 设计要点
|
|
15
17
|
|
|
@@ -25,6 +27,12 @@
|
|
|
25
27
|
name: '@elinpf/dsh-ops-access'
|
|
26
28
|
registryFile: ~/.dsh-ops/access.yaml # 默认值
|
|
27
29
|
credentialsDir: ~/.dsh-ops/credentials # 默认值;托管凭据内容文件(0600)
|
|
30
|
+
# source: hub # 可选;默认 yaml
|
|
31
|
+
# hubUrl: http://127.0.0.1:3090 # hub 来源:hub 服务地址
|
|
32
|
+
# hubToken: ... # hub 来源:读 token(或用环境变量 ACCESS_HUB_READ_TOKEN)
|
|
33
|
+
# hubAdminToken: ... # hub 来源:写 token(或用环境变量 ACCESS_HUB_ADMIN_TOKEN)
|
|
34
|
+
# hubCacheDir: ~/.dsh-ops/hub-cache # hub 来源:TTL 物化缓存目录(绝不用 credentialsDir)
|
|
35
|
+
# materializeTtlMinutes: 15 # hub 来源:缓存 TTL;启动时全量清扫
|
|
28
36
|
```
|
|
29
37
|
|
|
30
38
|
## 测试
|
package/lib/backend.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential-source backends for the ops-access seam.
|
|
3
|
+
*
|
|
4
|
+
* The `OpsAccess` handle (index.ts) owns policy — broker decisions, profile
|
|
5
|
+
* validation via provider schemas, envelope merge semantics, probes — and
|
|
6
|
+
* delegates raw entry persistence to an `AccessBackend`. Two backends exist:
|
|
7
|
+
*
|
|
8
|
+
* - `YamlBackend` — the original local YAML registry file (default).
|
|
9
|
+
* - `HubBackend` (hub-backend.ts) — a remote ops-access-hub service; secret
|
|
10
|
+
* content is fetched per resolve and materialized to managed local files.
|
|
11
|
+
*
|
|
12
|
+
* Both speak the same provider-shaped language: file fields are LOCAL PATHS
|
|
13
|
+
* in `fields` (the hub backend converts content ↔ path at its boundary), the
|
|
14
|
+
* envelope (`name`/`description`/`environment`) is per-entry, and a `probe`
|
|
15
|
+
* may ride beside each tier.
|
|
16
|
+
*
|
|
17
|
+
* @module @elinpf/dsh-ops-access/backend
|
|
18
|
+
*/
|
|
19
|
+
import type { EntryEnvelope, ProbeState } from './types.js';
|
|
20
|
+
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
21
|
+
/** Build an EntryEnvelope from raw entry data, taking each envelope field from the first source that has it. */
|
|
22
|
+
export declare function buildEnvelope(sources: Array<Record<string, unknown> | undefined>): EntryEnvelope;
|
|
23
|
+
/** Read a persisted probe result off a raw tier object (durable boundary — sanitize). */
|
|
24
|
+
export declare function probeOf(tierRaw: unknown): ProbeState | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Apply the envelope patch discipline to a mutable target: undefined field =
|
|
27
|
+
* preserve, empty string = delete, else set. Shared by both backends so the
|
|
28
|
+
* merge semantics stay identical across sources.
|
|
29
|
+
*/
|
|
30
|
+
export declare function applyEnvelopePatch(target: Record<string, unknown>, envelope: EntryEnvelope | undefined): void;
|
|
31
|
+
/** Merge an envelope patch over an existing envelope, returning the result (hub backend variant). */
|
|
32
|
+
export declare function mergeEnvelope(existing: EntryEnvelope, patch: EntryEnvelope | undefined): EntryEnvelope;
|
|
33
|
+
/** Tier presence + probe for the fields-free listing view. */
|
|
34
|
+
export interface BackendTierStatus {
|
|
35
|
+
probe?: ProbeState;
|
|
36
|
+
}
|
|
37
|
+
/** One entry in the fields-free listing view (listAll, resolve error hints). */
|
|
38
|
+
export interface BackendEntry {
|
|
39
|
+
kind: string;
|
|
40
|
+
name: string;
|
|
41
|
+
envelope: EntryEnvelope;
|
|
42
|
+
tiers: {
|
|
43
|
+
ro?: BackendTierStatus;
|
|
44
|
+
rw?: BackendTierStatus;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** One tier's raw data: provider-shaped fields (file fields are LOCAL PATHS) + envelope + probe. */
|
|
48
|
+
export interface BackendTier {
|
|
49
|
+
fields: Record<string, unknown>;
|
|
50
|
+
envelope: EntryEnvelope;
|
|
51
|
+
probe?: ProbeState;
|
|
52
|
+
}
|
|
53
|
+
/** Thrown when the source itself is unavailable (yaml: registry file missing) — resolve surfaces it verbatim. */
|
|
54
|
+
export declare class SourceUnavailableError extends Error {
|
|
55
|
+
}
|
|
56
|
+
export interface AccessBackend {
|
|
57
|
+
/**
|
|
58
|
+
* Human phrase for error messages, used as `in ${label}`: yaml →
|
|
59
|
+
* `registry file <path>`, hub → `access hub at <url>`.
|
|
60
|
+
*/
|
|
61
|
+
readonly label: string;
|
|
62
|
+
/**
|
|
63
|
+
* The fields-free listing of every entry: envelope + tier presence +
|
|
64
|
+
* probes. A missing source yields an empty list; an unreadable/corrupt
|
|
65
|
+
* source THROWS (callers pick their own degrade discipline, matching the
|
|
66
|
+
* pre-backend behavior per method).
|
|
67
|
+
*/
|
|
68
|
+
listEntries(): Promise<BackendEntry[]>;
|
|
69
|
+
/**
|
|
70
|
+
* One tier's provider-shaped fields + envelope. Null when the entry or the
|
|
71
|
+
* tier does not exist. Throws SourceUnavailableError when the source
|
|
72
|
+
* itself is missing, and rethrows read/parse failures.
|
|
73
|
+
*
|
|
74
|
+
* `materialize` (default true) matters only for the hub backend: true
|
|
75
|
+
* writes fetched file-field contents to managed local files (resolve —
|
|
76
|
+
* the credential is being issued); false substitutes the would-be managed
|
|
77
|
+
* path WITHOUT touching disk (canResolve/list/getEntry — metadata reads
|
|
78
|
+
* must not write secret material, e.g. the gate's pre-approval check).
|
|
79
|
+
*/
|
|
80
|
+
loadTier(kind: string, name: string, tier: 'ro' | 'rw', opts?: {
|
|
81
|
+
materialize?: boolean;
|
|
82
|
+
}): Promise<BackendTier | null>;
|
|
83
|
+
/**
|
|
84
|
+
* Persist one tier. `fields` is provider-shaped (file fields are local
|
|
85
|
+
* paths — the hub backend reads their content and uploads THAT; paths
|
|
86
|
+
* never leave the machine). `envelope` follows the patch discipline
|
|
87
|
+
* (undefined = preserve, '' = delete). `probe`, when given, is stored
|
|
88
|
+
* beside the tier.
|
|
89
|
+
*/
|
|
90
|
+
putTier(kind: string, name: string, tier: 'ro' | 'rw', fields: Record<string, unknown>, envelope: EntryEnvelope | undefined, probe?: ProbeState): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Remove one tier. Returns 'missing' when the entry did not exist, 'tier'
|
|
93
|
+
* when the tier is gone but the entry survives, 'entry' when the last
|
|
94
|
+
* tier went and the whole entry was dropped. (A tier that was already
|
|
95
|
+
* absent on an existing entry reports as if deleted — the pre-backend
|
|
96
|
+
* deleteEntry answered true for an existing entry regardless.)
|
|
97
|
+
*/
|
|
98
|
+
deleteTier(kind: string, name: string, tier: 'ro' | 'rw'): Promise<'missing' | 'tier' | 'entry'>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The original local YAML registry. Every operation re-reads (and writes
|
|
102
|
+
* back) the whole file — edits take effect immediately, nothing is cached.
|
|
103
|
+
* Sections whose kind has no registered provider are preserved untouched.
|
|
104
|
+
*/
|
|
105
|
+
export declare class YamlBackend implements AccessBackend {
|
|
106
|
+
readonly registryFile: string;
|
|
107
|
+
readonly label: string;
|
|
108
|
+
constructor(registryFile: string);
|
|
109
|
+
listEntries(): Promise<BackendEntry[]>;
|
|
110
|
+
loadTier(kind: string, name: string, tier: 'ro' | 'rw'): Promise<BackendTier | null>;
|
|
111
|
+
putTier(kind: string, name: string, tier: 'ro' | 'rw', fields: Record<string, unknown>, envelope: EntryEnvelope | undefined, probe?: ProbeState): Promise<void>;
|
|
112
|
+
deleteTier(kind: string, name: string, tier: 'ro' | 'rw'): Promise<'missing' | 'tier' | 'entry'>;
|
|
113
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hub backend: fetch credentials from a remote ops-access-hub service.
|
|
3
|
+
*
|
|
4
|
+
* The hub stores file-field CONTENT; this backend converts at the boundary:
|
|
5
|
+
*
|
|
6
|
+
* - `loadTier` downloads the tier's fields and MATERIALIZES each declared
|
|
7
|
+
* file field to a managed local file (`<credentialsDir>/<kind>/<name>/<tier>/<field>`,
|
|
8
|
+
* 0600, atomic write, skipped when the content is unchanged), substituting
|
|
9
|
+
* the local path — so downstream consumers (kubectl/ssh CLIs, provider
|
|
10
|
+
* schemas, probes) see exactly the same provider-shaped profile the YAML
|
|
11
|
+
* backend serves, and secret paths never leave the machine.
|
|
12
|
+
* - `putTier` reads the managed local files back and uploads their CONTENT
|
|
13
|
+
* (the write path — register_access, the admin UI — stages content files
|
|
14
|
+
* locally first, exactly as in YAML mode).
|
|
15
|
+
*
|
|
16
|
+
* Every call hits the hub — nothing is cached, mirroring the YAML backend's
|
|
17
|
+
* re-read-on-every-call discipline. Listing and metadata reads never carry
|
|
18
|
+
* field values; secret content crosses the wire only on the resolve and
|
|
19
|
+
* write paths, over the operator-managed channel (the hub binds loopback or
|
|
20
|
+
* sits behind a TLS-terminating reverse proxy).
|
|
21
|
+
*
|
|
22
|
+
* @module @elinpf/dsh-ops-access/hub-backend
|
|
23
|
+
*/
|
|
24
|
+
import type { AccessProvider, EntryEnvelope, ProbeState } from './types.js';
|
|
25
|
+
import type { AccessBackend, BackendEntry, BackendTier } from './backend.js';
|
|
26
|
+
export interface HubBackendOptions {
|
|
27
|
+
/** Hub base URL, trailing slashes stripped (e.g. `http://127.0.0.1:3090`). */
|
|
28
|
+
baseUrl: string;
|
|
29
|
+
/** Bearer token for reads (resolve/list); empty = anonymous. */
|
|
30
|
+
readToken: string;
|
|
31
|
+
/** Bearer token for writes (put/delete); empty = anonymous. */
|
|
32
|
+
adminToken: string;
|
|
33
|
+
/**
|
|
34
|
+
* Cache root for materialized credential files (already `~`-expanded).
|
|
35
|
+
* Deliberately NOT the yaml mode's credentialsDir: hub-mode local files are
|
|
36
|
+
* a TTL-bound cache, never permanent copies, and the sweeper must never
|
|
37
|
+
* touch files the yaml registry still references (the documented fallback).
|
|
38
|
+
*/
|
|
39
|
+
cacheDir: string;
|
|
40
|
+
/** Provider lookup — file-field declarations drive the content ↔ path conversion. */
|
|
41
|
+
getProvider: (kind: string) => AccessProvider | undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Delete materialized credential files older than maxAgeMs under
|
|
45
|
+
* credentialsDir (pass 0 to sweep everything), then remove the directories
|
|
46
|
+
* left empty. In hub mode every local credential file is a TTL-bound cache
|
|
47
|
+
* of hub content — never a permanent copy: resolve re-materializes on demand
|
|
48
|
+
* (writeIfChanged), so deletion is always safe and transparent to consumers.
|
|
49
|
+
* Best-effort: individual failures are skipped, the next sweep retries.
|
|
50
|
+
* Returns the number of files removed.
|
|
51
|
+
*/
|
|
52
|
+
export declare function sweepMaterialized(credentialsDir: string, maxAgeMs: number): Promise<number>;
|
|
53
|
+
export declare class HubBackend implements AccessBackend {
|
|
54
|
+
private readonly opts;
|
|
55
|
+
readonly label: string;
|
|
56
|
+
constructor(opts: HubBackendOptions);
|
|
57
|
+
/**
|
|
58
|
+
* One HTTP call. Returns null on 404; throws on every other failure with
|
|
59
|
+
* the hub's error message (which never carries field values). The auth
|
|
60
|
+
* token rides an Authorization header and never lands in error text.
|
|
61
|
+
*/
|
|
62
|
+
private request;
|
|
63
|
+
listEntries(): Promise<BackendEntry[]>;
|
|
64
|
+
loadTier(kind: string, name: string, tier: 'ro' | 'rw', loadOpts?: {
|
|
65
|
+
materialize?: boolean;
|
|
66
|
+
}): Promise<BackendTier | null>;
|
|
67
|
+
putTier(kind: string, name: string, tier: 'ro' | 'rw', fields: Record<string, unknown>, envelope: EntryEnvelope | undefined, probe?: ProbeState): Promise<void>;
|
|
68
|
+
deleteTier(kind: string, name: string, tier: 'ro' | 'rw'): Promise<'missing' | 'tier' | 'entry'>;
|
|
69
|
+
submitRequest(req: {
|
|
70
|
+
kind: string;
|
|
71
|
+
name: string;
|
|
72
|
+
tier: 'ro' | 'rw';
|
|
73
|
+
fields: Record<string, unknown>;
|
|
74
|
+
envelope?: EntryEnvelope;
|
|
75
|
+
reason?: string;
|
|
76
|
+
}): Promise<string>;
|
|
77
|
+
/** Pending-request metadata for the approval UI — field values never cross. */
|
|
78
|
+
listRequests(status?: 'pending' | 'approved' | 'rejected'): Promise<unknown>;
|
|
79
|
+
/** Full request incl. field values, for pre-approval review. Null when absent. */
|
|
80
|
+
getRequest(id: string): Promise<unknown>;
|
|
81
|
+
/** Approve (hub writes the tier) or reject. Returns false when already settled/absent. */
|
|
82
|
+
decideRequest(id: string, approved: boolean): Promise<boolean>;
|
|
83
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ops access capability seam.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* leaves the filesystem — profiles carry only paths and connection params.
|
|
4
|
+
* Exposes `ctx.opsAccess`: a generic `resolve(kind, name)` / `list()` entry
|
|
5
|
+
* plus a `register(provider)` surface for provider plugins. Providers (one
|
|
6
|
+
* per credential kind) supply the zod schema for their entry shape and an
|
|
7
|
+
* optional `process` step (e.g. `~` expansion); profiles carry only paths
|
|
8
|
+
* and connection params, never inline secret material.
|
|
10
9
|
*
|
|
11
|
-
* The
|
|
12
|
-
*
|
|
10
|
+
* The credential SOURCE is pluggable (see backend.ts):
|
|
11
|
+
* - 'yaml' (default) owns the local YAML registry file
|
|
12
|
+
* (`~/.dsh-ops/access.yaml`), re-read, re-parsed, and re-validated on
|
|
13
|
+
* every call — edits take effect immediately, nothing is cached.
|
|
14
|
+
* - 'hub' fetches entries from a remote ops-access-hub service on every
|
|
15
|
+
* call (hub-backend.ts): file-field CONTENT is materialized to managed
|
|
16
|
+
* local files at resolve time, so profiles still carry only paths.
|
|
13
17
|
*
|
|
14
18
|
* Also registers the `register_access` tool: the agent's self-service path
|
|
15
|
-
* for writing the ro tier of a profile
|
|
16
|
-
*
|
|
19
|
+
* for writing the ro tier of a profile. rw tiers stay human-approved — the
|
|
20
|
+
* tool can only QUEUE an rw registration request on the hub (hub mode);
|
|
21
|
+
* a human approves it in the admin UI before anything is written.
|
|
17
22
|
*
|
|
18
|
-
* Registry format:
|
|
23
|
+
* Registry format (yaml source):
|
|
19
24
|
*
|
|
20
25
|
* ```yaml
|
|
21
26
|
* version: 1
|
|
@@ -42,10 +47,37 @@ import type { AccessProvider, AccessBroker, OpsAccess } from './types.js';
|
|
|
42
47
|
export declare const name = "ops-access";
|
|
43
48
|
export declare const inject: string[];
|
|
44
49
|
export interface Config {
|
|
45
|
-
/** Path to the YAML access registry; a leading `~` expands to $HOME. */
|
|
50
|
+
/** Path to the YAML access registry; a leading `~` expands to $HOME. (source: 'yaml') */
|
|
46
51
|
registryFile: string;
|
|
47
52
|
/** Root directory for managed credential content files; a leading `~` expands to $HOME. */
|
|
48
53
|
credentialsDir: string;
|
|
54
|
+
/**
|
|
55
|
+
* Credential source: 'yaml' (default) reads the local registry file;
|
|
56
|
+
* 'hub' fetches entries from a remote ops-access-hub service on every
|
|
57
|
+
* call and materializes file-field content to managed local files.
|
|
58
|
+
*/
|
|
59
|
+
source?: 'yaml' | 'hub';
|
|
60
|
+
/** Hub base URL (source: 'hub'), e.g. http://127.0.0.1:3090. */
|
|
61
|
+
hubUrl?: string;
|
|
62
|
+
/** Hub read token (source: 'hub'); falls back to env ACCESS_HUB_READ_TOKEN. Never logged. */
|
|
63
|
+
hubToken?: string;
|
|
64
|
+
/** Hub admin token for write/delete (source: 'hub'); falls back to env ACCESS_HUB_ADMIN_TOKEN. Never logged. */
|
|
65
|
+
hubAdminToken?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Minutes a materialized credential file may linger on this host (source:
|
|
68
|
+
* 'hub'). Materialized files are a TTL-bound cache of hub content, never a
|
|
69
|
+
* permanent copy — resolve re-materializes on demand, so expiry is
|
|
70
|
+
* transparent to consumers. Startup sweeps everything (a restart clears
|
|
71
|
+
* the grant ledger; cached rw material must not outlive it).
|
|
72
|
+
*/
|
|
73
|
+
materializeTtlMinutes?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Cache root for hub-mode materialized files (source: 'hub'; default
|
|
76
|
+
* `~/.dsh-ops/hub-cache`). Deliberately separate from credentialsDir: the
|
|
77
|
+
* sweeper only ever walks this dir, so files the yaml registry references
|
|
78
|
+
* (the documented fallback) are never touched.
|
|
79
|
+
*/
|
|
80
|
+
hubCacheDir?: string;
|
|
49
81
|
}
|
|
50
82
|
export declare const Config: z<Config>;
|
|
51
83
|
export type { AccessProvider, AccessProfile, EntryEnvelope, ProbeState, AdminTierStatus, AdminEntry, KindDescriptor, AccessAgent, AccessBrokerDecision, AccessBroker, OpsAccess, } from './types.js';
|