@memnexus-ai/mx-agent-cli 0.1.176 → 0.1.177
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/__tests__/coordination-token-multitenant.test.d.ts +11 -0
- package/dist/__tests__/coordination-token-multitenant.test.d.ts.map +1 -0
- package/dist/__tests__/coordination-token-multitenant.test.js +106 -0
- package/dist/__tests__/coordination-token-multitenant.test.js.map +1 -0
- package/dist/__tests__/coordination-token.test.js +8 -8
- package/dist/__tests__/coordination-token.test.js.map +1 -1
- package/dist/agent-config/coordination-rosters.generated.json +15 -0
- package/dist/agent-config/hooks/coordination-brief.sh +53 -16
- package/dist/lib/coordination-token.d.ts +46 -15
- package/dist/lib/coordination-token.d.ts.map +1 -1
- package/dist/lib/coordination-token.js +108 -47
- package/dist/lib/coordination-token.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* coordination-token — multi-tenant (Phase 1).
|
|
3
|
+
*
|
|
4
|
+
* Verifies: a NON-default project keys its secret + cache separately; the
|
|
5
|
+
* DEFAULT project (memnexus) falls back to the legacy single-hyphen KV name (the
|
|
6
|
+
* zero-orphan dual-read against the un-migrated vault); and the roster resolves
|
|
7
|
+
* from the OFFLINE generated file with NO session-start HTTP (ND-2 — the resolver
|
|
8
|
+
* only ever touches readFileText + fetchSecret, never the coordination service).
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=coordination-token-multitenant.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coordination-token-multitenant.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/coordination-token-multitenant.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* coordination-token — multi-tenant (Phase 1).
|
|
3
|
+
*
|
|
4
|
+
* Verifies: a NON-default project keys its secret + cache separately; the
|
|
5
|
+
* DEFAULT project (memnexus) falls back to the legacy single-hyphen KV name (the
|
|
6
|
+
* zero-orphan dual-read against the un-migrated vault); and the roster resolves
|
|
7
|
+
* from the OFFLINE generated file with NO session-start HTTP (ND-2 — the resolver
|
|
8
|
+
* only ever touches readFileText + fetchSecret, never the coordination service).
|
|
9
|
+
*/
|
|
10
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
11
|
+
vi.mock('../lib/config.js', () => ({ getConfigValue: vi.fn(() => undefined) }));
|
|
12
|
+
import { resolveCoordinationToken, resolveRoster, kvSecretName, KEYVAULT_NAME, ENV_KEY_ROSTER_FILE, } from '../lib/coordination-token.js';
|
|
13
|
+
const ROSTER_PATH = '/fake/coordination-rosters.generated.json';
|
|
14
|
+
const ROSTER_JSON = JSON.stringify({
|
|
15
|
+
version: 1,
|
|
16
|
+
projects: { memnexus: ['retrieval', 'mcp'], spearmint: ['retrieval', 'mcp'] },
|
|
17
|
+
});
|
|
18
|
+
/** In-memory readFileText backed by a path→contents map. */
|
|
19
|
+
function reader(files) {
|
|
20
|
+
return (p) => (p in files ? files[p] : undefined);
|
|
21
|
+
}
|
|
22
|
+
describe('coordination-token multi-tenant (Phase 1)', () => {
|
|
23
|
+
it('a NON-default project keys its secret + cache separately (spearmint)', () => {
|
|
24
|
+
const marker = '/wt/.mx-coordination-team';
|
|
25
|
+
const fetchSecret = vi.fn((_v, name) => (name === kvSecretName('spearmint', 'retrieval') ? 'spear-tok' : undefined));
|
|
26
|
+
const writeCache = vi.fn();
|
|
27
|
+
const res = resolveCoordinationToken({
|
|
28
|
+
project: 'spearmint',
|
|
29
|
+
env: { CLAUDE_WORKTREE_PATH: '/wt', [ENV_KEY_ROSTER_FILE]: ROSTER_PATH },
|
|
30
|
+
readFileText: reader({ [marker]: 'retrieval\n', [ROSTER_PATH]: ROSTER_JSON }),
|
|
31
|
+
fetchSecret,
|
|
32
|
+
writeCache,
|
|
33
|
+
});
|
|
34
|
+
expect(res.status).toBe('resolved');
|
|
35
|
+
expect(res.project).toBe('spearmint');
|
|
36
|
+
expect(res.token).toBe('spear-tok');
|
|
37
|
+
// Fetched the spearmint-namespaced secret, never a memnexus/legacy name.
|
|
38
|
+
expect(fetchSecret).toHaveBeenCalledWith(KEYVAULT_NAME, 'coordination-token--spearmint--retrieval');
|
|
39
|
+
expect(fetchSecret).not.toHaveBeenCalledWith(KEYVAULT_NAME, 'coordination-token-retrieval');
|
|
40
|
+
// Cache path embeds BOTH slugs.
|
|
41
|
+
expect(writeCache).toHaveBeenCalledWith('/wt/.mx-coordination-token--spearmint--retrieval', 'spear-tok');
|
|
42
|
+
});
|
|
43
|
+
it('memnexus falls back to the LEGACY single-hyphen KV name (zero-orphan dual-read)', () => {
|
|
44
|
+
const marker = '/wt/.mx-coordination-team';
|
|
45
|
+
// Primary double-hyphen name is ABSENT (un-migrated vault); legacy name present.
|
|
46
|
+
const fetchSecret = vi.fn((_v, name) => (name === 'coordination-token-retrieval' ? 'legacy-tok' : undefined));
|
|
47
|
+
const res = resolveCoordinationToken({
|
|
48
|
+
project: 'memnexus',
|
|
49
|
+
env: { CLAUDE_WORKTREE_PATH: '/wt', [ENV_KEY_ROSTER_FILE]: ROSTER_PATH },
|
|
50
|
+
readFileText: reader({ [marker]: 'retrieval\n', [ROSTER_PATH]: ROSTER_JSON }),
|
|
51
|
+
fetchSecret,
|
|
52
|
+
writeCache: vi.fn(),
|
|
53
|
+
});
|
|
54
|
+
expect(res.status).toBe('resolved');
|
|
55
|
+
expect(res.token).toBe('legacy-tok');
|
|
56
|
+
// It tried the parse-safe name FIRST, then fell back to the legacy name.
|
|
57
|
+
expect(fetchSecret).toHaveBeenNthCalledWith(1, KEYVAULT_NAME, 'coordination-token--memnexus--retrieval');
|
|
58
|
+
expect(fetchSecret).toHaveBeenNthCalledWith(2, KEYVAULT_NAME, 'coordination-token-retrieval');
|
|
59
|
+
});
|
|
60
|
+
it('a NON-default project does NOT fall back to the legacy name (only memnexus does)', () => {
|
|
61
|
+
const marker = '/wt/.mx-coordination-team';
|
|
62
|
+
const fetchSecret = vi.fn(() => undefined); // nothing found
|
|
63
|
+
const res = resolveCoordinationToken({
|
|
64
|
+
project: 'spearmint',
|
|
65
|
+
env: { CLAUDE_WORKTREE_PATH: '/wt', [ENV_KEY_ROSTER_FILE]: ROSTER_PATH },
|
|
66
|
+
readFileText: reader({ [marker]: 'retrieval\n', [ROSTER_PATH]: ROSTER_JSON }),
|
|
67
|
+
fetchSecret,
|
|
68
|
+
writeCache: vi.fn(),
|
|
69
|
+
});
|
|
70
|
+
expect(res.status).toBe('unprovisioned');
|
|
71
|
+
// Only the spearmint-namespaced name was tried — never the legacy fallback.
|
|
72
|
+
expect(fetchSecret).toHaveBeenCalledTimes(1);
|
|
73
|
+
expect(fetchSecret).toHaveBeenCalledWith(KEYVAULT_NAME, 'coordination-token--spearmint--retrieval');
|
|
74
|
+
});
|
|
75
|
+
it('fails CLOSED when the team is not in the TARGET project roster', () => {
|
|
76
|
+
const marker = '/wt/.mx-coordination-team';
|
|
77
|
+
const fetchSecret = vi.fn();
|
|
78
|
+
const res = resolveCoordinationToken({
|
|
79
|
+
project: 'spearmint',
|
|
80
|
+
env: { CLAUDE_WORKTREE_PATH: '/wt', [ENV_KEY_ROSTER_FILE]: ROSTER_PATH },
|
|
81
|
+
// 'platform' is in memnexus's roster but NOT spearmint's.
|
|
82
|
+
readFileText: reader({ [marker]: 'platform\n', [ROSTER_PATH]: ROSTER_JSON }),
|
|
83
|
+
fetchSecret,
|
|
84
|
+
writeCache: vi.fn(),
|
|
85
|
+
});
|
|
86
|
+
expect(res.status).toBe('not-on-roster');
|
|
87
|
+
expect(fetchSecret).not.toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('offline roster resolution (ND-2 — no session-start HTTP)', () => {
|
|
91
|
+
it('resolves the roster from the OFFLINE file (service down is irrelevant — no HTTP is made)', () => {
|
|
92
|
+
const teams = resolveRoster('spearmint', { [ENV_KEY_ROSTER_FILE]: ROSTER_PATH }, reader({ [ROSTER_PATH]: ROSTER_JSON }));
|
|
93
|
+
expect(teams).toEqual(['retrieval', 'mcp']);
|
|
94
|
+
});
|
|
95
|
+
it('falls back to the BUILTIN memnexus roster when the file is unreadable (offline-safe)', () => {
|
|
96
|
+
// No file at all → builtin memnexus fallback, still no network.
|
|
97
|
+
const teams = resolveRoster('memnexus', {}, reader({}));
|
|
98
|
+
expect(teams).toContain('platform');
|
|
99
|
+
expect(teams).toContain('retrieval');
|
|
100
|
+
});
|
|
101
|
+
it('returns an empty roster for an unknown project with no file (fail-closed, offline)', () => {
|
|
102
|
+
const teams = resolveRoster('unknown-project', {}, reader({}));
|
|
103
|
+
expect(teams).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
//# sourceMappingURL=coordination-token-multitenant.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coordination-token-multitenant.test.js","sourceRoot":"","sources":["../../src/__tests__/coordination-token-multitenant.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -46,7 +46,7 @@ describe('resolveCoordinationToken', () => {
|
|
|
46
46
|
it('uses the per-worktree cache when present and never calls Key Vault', () => {
|
|
47
47
|
const fetchSecret = vi.fn();
|
|
48
48
|
const marker = join(dir, '.mx-coordination-team');
|
|
49
|
-
const cache = join(dir, '.mx-coordination-token
|
|
49
|
+
const cache = join(dir, '.mx-coordination-token--memnexus--eval');
|
|
50
50
|
const res = resolveCoordinationToken({
|
|
51
51
|
env: { CLAUDE_WORKTREE_PATH: dir },
|
|
52
52
|
readFileText: reader({ [marker]: 'eval\n', [cache]: 'cached-eval-tok\n' }),
|
|
@@ -61,15 +61,15 @@ describe('resolveCoordinationToken', () => {
|
|
|
61
61
|
// Real fs so we can assert the on-disk cache mode.
|
|
62
62
|
writeFileSync(join(dir, '.mx-coordination-team'), 'eval\n');
|
|
63
63
|
const fetchSecret = vi.fn((_vault, name) => {
|
|
64
|
-
expect(name).toBe('coordination-token
|
|
64
|
+
expect(name).toBe('coordination-token--memnexus--eval');
|
|
65
65
|
return 'kv-fetched-tok';
|
|
66
66
|
});
|
|
67
67
|
const res = resolveCoordinationToken({ env: { CLAUDE_WORKTREE_PATH: dir }, fetchSecret });
|
|
68
68
|
expect(res.status).toBe('resolved');
|
|
69
69
|
expect(res.source).toBe('keyvault');
|
|
70
70
|
expect(res.token).toBe('kv-fetched-tok');
|
|
71
|
-
expect(fetchSecret).toHaveBeenCalledWith(KEYVAULT_NAME, 'coordination-token
|
|
72
|
-
const cachePath = join(dir, '.mx-coordination-token
|
|
71
|
+
expect(fetchSecret).toHaveBeenCalledWith(KEYVAULT_NAME, 'coordination-token--memnexus--eval');
|
|
72
|
+
const cachePath = join(dir, '.mx-coordination-token--memnexus--eval');
|
|
73
73
|
expect(existsSync(cachePath)).toBe(true);
|
|
74
74
|
expect(readFileSync(cachePath, 'utf-8')).toBe('kv-fetched-tok');
|
|
75
75
|
// SF-3: born 0600 — no group/other bits.
|
|
@@ -92,7 +92,7 @@ describe('resolveCoordinationToken', () => {
|
|
|
92
92
|
// The worktree was recycled from eval to marketing: eval's cache lingers, but
|
|
93
93
|
// the marketing marker must fetch marketing's token, never reuse eval's.
|
|
94
94
|
const marker = join(dir, '.mx-coordination-team');
|
|
95
|
-
const evalCache = join(dir, '.mx-coordination-token
|
|
95
|
+
const evalCache = join(dir, '.mx-coordination-token--memnexus--eval');
|
|
96
96
|
const fetchSecret = vi.fn(() => 'marketing-tok');
|
|
97
97
|
const res = resolveCoordinationToken({
|
|
98
98
|
env: { CLAUDE_WORKTREE_PATH: dir },
|
|
@@ -103,7 +103,7 @@ describe('resolveCoordinationToken', () => {
|
|
|
103
103
|
expect(res.token).toBe('marketing-tok');
|
|
104
104
|
expect(res.token).not.toBe('STALE-EVAL-TOKEN');
|
|
105
105
|
// It fetched marketing's secret, never touched eval's.
|
|
106
|
-
expect(fetchSecret).toHaveBeenCalledWith(KEYVAULT_NAME, 'coordination-token
|
|
106
|
+
expect(fetchSecret).toHaveBeenCalledWith(KEYVAULT_NAME, 'coordination-token--memnexus--marketing');
|
|
107
107
|
});
|
|
108
108
|
it('fails OPEN when Key Vault / az errors — reports unprovisioned, never throws', () => {
|
|
109
109
|
const marker = join(dir, '.mx-coordination-team');
|
|
@@ -136,7 +136,7 @@ describe('resolveCoordinationToken', () => {
|
|
|
136
136
|
it('SF-A: never throws when the config read throws (unwritable HOME) — continues to the marker path', () => {
|
|
137
137
|
mockGetConfigValue.mockImplementation(() => { throw new Error("EACCES: permission denied, mkdir '/home/nobody/.mx-agent'"); });
|
|
138
138
|
const marker = join(dir, '.mx-coordination-team');
|
|
139
|
-
const cache = join(dir, '.mx-coordination-token
|
|
139
|
+
const cache = join(dir, '.mx-coordination-token--memnexus--eval');
|
|
140
140
|
let res;
|
|
141
141
|
expect(() => {
|
|
142
142
|
res = resolveCoordinationToken({
|
|
@@ -153,7 +153,7 @@ describe('resolveCoordinationToken', () => {
|
|
|
153
153
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
154
154
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
155
155
|
const marker = join(dir, '.mx-coordination-team');
|
|
156
|
-
const cache = join(dir, '.mx-coordination-token
|
|
156
|
+
const cache = join(dir, '.mx-coordination-token--memnexus--eval');
|
|
157
157
|
resolveCoordinationToken({
|
|
158
158
|
env: { CLAUDE_WORKTREE_PATH: dir },
|
|
159
159
|
readFileText: reader({ [marker]: 'eval\n', [cache]: 'secret-token-value' }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coordination-token.test.js","sourceRoot":"","sources":["../../src/__tests__/coordination-token.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC5F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAE5B,8EAA8E;AAC9E,+EAA+E;AAC/E,oFAAoF;AACpF,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;AAEhF,OAAO,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,kBAAkB,GAAG,cAAqD,CAAC;AAEjF,IAAI,GAAW,CAAC;AAEhB,UAAU,CAAC,GAAG,EAAE;IACd,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;IAClD,kBAAkB,CAAC,SAAS,EAAE,CAAC;IAC/B,kBAAkB,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC;AACH,SAAS,CAAC,GAAG,EAAE;IACb,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,EAAE,CAAC,eAAe,EAAE,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,4DAA4D;AAC5D,SAAS,MAAM,CAAC,KAA6B;IAC3C,OAAO,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAC5D,CAAC;AAED,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,wBAAwB,CAAC;YACnC,GAAG,EAAE,EAAE,sBAAsB,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,EAAE;YACrE,WAAW;SACZ,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC9C,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAClC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"coordination-token.test.js","sourceRoot":"","sources":["../../src/__tests__/coordination-token.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -53,11 +53,19 @@ KEYVAULT_NAME="mx-dev-eastus2-kv-001"
|
|
|
53
53
|
MIN_CLI_VERSION="0.1.172"
|
|
54
54
|
CLI_UPDATE_CMD="npm install -g @memnexus-ai/mx-agent-cli@latest"
|
|
55
55
|
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
|
|
56
|
+
# MULTI-TENANT (Phase 1). Identity is (project, team). `project` is the committed
|
|
57
|
+
# project-config signal (resolveProductSlug precedence: MX_PRODUCT_SLUG →
|
|
58
|
+
# mx-agent.config.json projectSlug → memnexus). `team` is the marker slug. The
|
|
59
|
+
# roster is resolved from the OFFLINE generated file (ND-2) — NEVER a live
|
|
60
|
+
# GET /roster at session start, so a coordination-service outage (the shared
|
|
61
|
+
# SPOF) can't break session start. For the DEFAULT project (memnexus) behavior is
|
|
62
|
+
# IDENTICAL to before: same roster membership, same token (via the legacy KV
|
|
63
|
+
# name fallback below).
|
|
64
|
+
DEFAULT_PROJECT="memnexus"
|
|
65
|
+
# BUILTIN memnexus roster — OFFLINE fallback used only when the generated file is
|
|
66
|
+
# unreadable (or node is unavailable). Mirrors PROVISIONED_ROSTERS in
|
|
67
|
+
# coordination-service/src/config/rosters.ts.
|
|
68
|
+
BUILTIN_MEMNEXUS_ROSTER="platform retrieval mcp pipeline marketing customer-portal eval product"
|
|
61
69
|
|
|
62
70
|
# ── MF-2: authoritative marker, hook NEVER derives the slug. ─────────────────
|
|
63
71
|
# The slug is an explicit assignment written into the worktree by an onboarding
|
|
@@ -80,22 +88,41 @@ if [ -z "$SLUG" ]; then
|
|
|
80
88
|
exit 0
|
|
81
89
|
fi
|
|
82
90
|
|
|
83
|
-
# ──
|
|
91
|
+
# ── Resolve the PROJECT (resolveProductSlug precedence, offline). ────────────
|
|
92
|
+
# 1) MX_PRODUCT_SLUG env; 2) mx-agent.config.json projectSlug at the worktree
|
|
93
|
+
# root; 3) default memnexus. No HTTP — reads local config only.
|
|
94
|
+
PROJECT="${MX_PRODUCT_SLUG:-}"
|
|
95
|
+
if [ -z "$PROJECT" ] && [ -f "${WORKTREE}/mx-agent.config.json" ]; then
|
|
96
|
+
PROJECT="$(node -e 'try{const c=require(process.argv[1]);const s=(c.projectSlug||(c.project&&c.project.slug)||"");process.stdout.write(String(s).trim())}catch(e){}' "${WORKTREE}/mx-agent.config.json" 2>/dev/null || true)"
|
|
97
|
+
fi
|
|
98
|
+
[ -z "$PROJECT" ] && PROJECT="$DEFAULT_PROJECT"
|
|
99
|
+
|
|
100
|
+
# ── SF-2: per-project roster-membership validation before any az call. ───────
|
|
101
|
+
# The roster is read from the OFFLINE generated file (ND-2), with the builtin
|
|
102
|
+
# memnexus roster as an offline fallback. NEVER a live GET /roster.
|
|
103
|
+
ROSTER_FILE="${MX_COORD_ROSTER_FILE:-${WORKTREE}/mx-agent-system/agent-config/coordination-rosters.generated.json}"
|
|
104
|
+
ROSTER_TEAMS=""
|
|
105
|
+
if [ -f "$ROSTER_FILE" ]; then
|
|
106
|
+
ROSTER_TEAMS="$(node -e 'try{const r=require(process.argv[1]);const t=(r.projects&&r.projects[process.argv[2]])||[];process.stdout.write(t.join(" "))}catch(e){}' "$ROSTER_FILE" "$PROJECT" 2>/dev/null || true)"
|
|
107
|
+
fi
|
|
108
|
+
if [ -z "$ROSTER_TEAMS" ] && [ "$PROJECT" = "$DEFAULT_PROJECT" ]; then
|
|
109
|
+
# Offline fallback: file missing/unparseable/node absent → builtin memnexus roster.
|
|
110
|
+
ROSTER_TEAMS="$BUILTIN_MEMNEXUS_ROSTER"
|
|
111
|
+
fi
|
|
84
112
|
in_roster=""
|
|
85
|
-
for t in
|
|
113
|
+
for t in $ROSTER_TEAMS; do
|
|
86
114
|
if [ "$SLUG" = "$t" ]; then in_roster="yes"; break; fi
|
|
87
115
|
done
|
|
88
116
|
if [ -z "$in_roster" ]; then
|
|
89
|
-
# Unknown/invalid
|
|
90
|
-
echo "coordination brief: team
|
|
117
|
+
# Unknown/invalid (project, team) → fail-closed. Never fetch a default token.
|
|
118
|
+
echo "coordination brief: (project \"$PROJECT\", team \"$SLUG\") not in roster — skipping" >&2
|
|
91
119
|
exit 0
|
|
92
120
|
fi
|
|
93
121
|
|
|
94
|
-
# ── MF-1:
|
|
95
|
-
# The cache filename embeds
|
|
96
|
-
#
|
|
97
|
-
|
|
98
|
-
CACHE="${WORKTREE}/.mx-coordination-token-${SLUG}"
|
|
122
|
+
# ── MF-1: (project, team)-bound per-worktree token cache. ────────────────────
|
|
123
|
+
# The cache filename embeds BOTH slugs, so a worktree recycled to another
|
|
124
|
+
# team/project can NEVER reuse the prior tenant's cached token.
|
|
125
|
+
CACHE="${WORKTREE}/.mx-coordination-token--${PROJECT}--${SLUG}"
|
|
99
126
|
TOKEN=""
|
|
100
127
|
if [ -s "$CACHE" ]; then
|
|
101
128
|
TOKEN="$(head -n1 "$CACHE" 2>/dev/null || true)"
|
|
@@ -104,11 +131,21 @@ fi
|
|
|
104
131
|
# ── Fetch from Key Vault once if not cached (SF-3 born-0600 write). ───────────
|
|
105
132
|
if [ -z "$TOKEN" ]; then
|
|
106
133
|
# Fail-OPEN on any fetch problem (not logged in, no access, missing secret).
|
|
107
|
-
# SLUG
|
|
134
|
+
# PROJECT/SLUG are roster-validated above; each is a single quoted argv element.
|
|
135
|
+
# ND-1: primary name is the parse-safe double-hyphen coordination-token--<p>--<t>.
|
|
108
136
|
fetched="$(az keyvault secret show \
|
|
109
137
|
--vault-name "$KEYVAULT_NAME" \
|
|
110
|
-
--name "coordination-token
|
|
138
|
+
--name "coordination-token--${PROJECT}--${SLUG}" \
|
|
111
139
|
--query value -o tsv 2>/dev/null || true)"
|
|
140
|
+
# ZERO-ORPHAN dual-read (memnexus only): the un-migrated live vault still holds
|
|
141
|
+
# the legacy single-hyphen secret. Fall back to it so memnexus session-start is
|
|
142
|
+
# identical to today. Phase 2 renames the secret and removes this fallback.
|
|
143
|
+
if [ -z "$fetched" ] && [ "$PROJECT" = "$DEFAULT_PROJECT" ]; then
|
|
144
|
+
fetched="$(az keyvault secret show \
|
|
145
|
+
--vault-name "$KEYVAULT_NAME" \
|
|
146
|
+
--name "coordination-token-${SLUG}" \
|
|
147
|
+
--query value -o tsv 2>/dev/null || true)"
|
|
148
|
+
fi
|
|
112
149
|
if [ -z "$fetched" ]; then
|
|
113
150
|
# No token available — tolerate and skip (fail-open).
|
|
114
151
|
exit 0
|
|
@@ -4,34 +4,54 @@
|
|
|
4
4
|
* Ports the security-reviewed KV-fetch logic from the agent-config hook
|
|
5
5
|
* `coordination-brief.sh` into the CLI so that a bare `mx-agent coordination
|
|
6
6
|
* brief` (and drain/escalate/history) resolves its own token when none is
|
|
7
|
-
* configured
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* configured. MULTI-TENANT (Phase 1): identity is (project, team). `project`
|
|
8
|
+
* comes from resolveProductSlug() (config-derived, default 'memnexus'); `team`
|
|
9
|
+
* from the authoritative worktree marker. The isolation properties the security
|
|
10
|
+
* review required are preserved 1:1, now project-aware:
|
|
10
11
|
*
|
|
11
12
|
* - MF-2 The team slug is read from the authoritative worktree marker
|
|
12
13
|
* (.mx-coordination-team). It is NEVER derived from CLAUDE_TEAM_NAME
|
|
13
|
-
* or the worktree name.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* or the worktree name. The PROJECT is the committed project-config
|
|
15
|
+
* signal (resolveProductSlug), never a queue-mode client assertion.
|
|
16
|
+
* - SF-2 The (project, team) is validated against the per-project roster
|
|
17
|
+
* BEFORE any `az` call. A non-member fails CLOSED (no fetch).
|
|
18
|
+
* - MF-1 The token cache filename embeds BOTH slugs
|
|
19
|
+
* (.mx-coordination-token--<project>--<team>), per worktree. A worktree
|
|
20
|
+
* recycled to a different team/project can never read another tenant's
|
|
21
|
+
* cached token.
|
|
19
22
|
* - SF-3 The cache is created born 0600 (mode on create — no readable window).
|
|
20
|
-
* - No shell: the roster-validated
|
|
23
|
+
* - No shell: the roster-validated slugs are passed to `az` via execFile with an
|
|
21
24
|
* args array, never interpolated into a shell string.
|
|
22
|
-
* - The token is NEVER written to the global ~/.mx-agent config
|
|
23
|
-
* across worktrees — the MF-2 defect) and is NEVER logged.
|
|
25
|
+
* - The token is NEVER written to the global ~/.mx-agent config and NEVER logged.
|
|
24
26
|
* - Fail-OPEN on infra errors (no marker, az not logged in, KV unreachable):
|
|
25
27
|
* report "unprovisioned" so the caller degrades, never crash.
|
|
26
28
|
* - Fail-CLOSED only on a roster mismatch.
|
|
29
|
+
*
|
|
30
|
+
* ZERO-ORPHAN KV shim (Phase 1): the secret name is the parse-safe double-hyphen
|
|
31
|
+
* `coordination-token--<project>--<team>` (ND-1). For the DEFAULT project
|
|
32
|
+
* (memnexus) the live Key Vault still holds the PRE-migration single-hyphen
|
|
33
|
+
* `coordination-token-<team>` secrets, so the fetch falls back to the legacy name
|
|
34
|
+
* when the double-hyphen name is absent (the PRD migration grace-window dual-read).
|
|
35
|
+
* This keeps memnexus session-start working IDENTICALLY against the un-migrated
|
|
36
|
+
* vault; Phase 2 renames the secrets and the fallback is removed.
|
|
27
37
|
*/
|
|
28
38
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
39
|
+
* BUILTIN memnexus provisioned-team roster — the OFFLINE fallback used only when
|
|
40
|
+
* the generated roster file cannot be read. Mirrors PROVISIONED_ROSTERS in
|
|
41
|
+
* coordination-service/src/config/rosters.ts. Keeping it here preserves
|
|
42
|
+
* offline-safety (a missing file never breaks the memnexus gate) without a live
|
|
43
|
+
* HTTP dependency. Aliases mx-agent / mx-agent-system are intentionally excluded.
|
|
32
44
|
*/
|
|
33
45
|
export declare const COORD_ROSTER: readonly ["platform", "retrieval", "mcp", "pipeline", "marketing", "customer-portal", "eval", "product"];
|
|
34
46
|
export declare const KEYVAULT_NAME = "mx-dev-eastus2-kv-001";
|
|
47
|
+
/** Env override for the offline roster file path (test seam / operator override). */
|
|
48
|
+
export declare const ENV_KEY_ROSTER_FILE = "MX_COORD_ROSTER_FILE";
|
|
49
|
+
/**
|
|
50
|
+
* ND-1 single-source KV secret name shape — reproduced here (the CLI is a
|
|
51
|
+
* separate package from coordination-service). MUST stay byte-identical to
|
|
52
|
+
* kvSecretName() in coordination-service/src/types.ts.
|
|
53
|
+
*/
|
|
54
|
+
export declare function kvSecretName(project: string, team: string): string;
|
|
35
55
|
export type TokenResolveStatus =
|
|
36
56
|
/** A token was already in env/config; KV is skipped (hook path + manual override). */
|
|
37
57
|
'already-configured'
|
|
@@ -45,6 +65,8 @@ export interface TokenResolveResult {
|
|
|
45
65
|
status: TokenResolveStatus;
|
|
46
66
|
/** The resolved token — set only for 'already-configured' and 'resolved'. */
|
|
47
67
|
token?: string;
|
|
68
|
+
/** The resolved project (config-derived). */
|
|
69
|
+
project?: string;
|
|
48
70
|
/** The marker slug, when one was read (roster or not). */
|
|
49
71
|
slug?: string;
|
|
50
72
|
/** Where the token came from — for diagnostics; never includes the token. */
|
|
@@ -64,7 +86,16 @@ export interface TokenResolveDeps {
|
|
|
64
86
|
readFileText?: (path: string) => string | undefined;
|
|
65
87
|
/** Persist the fetched token to the per-worktree cache (born 0600). */
|
|
66
88
|
writeCache?: (path: string, value: string) => void;
|
|
89
|
+
/** Override the resolved project (test seam); defaults to resolveProductSlug(). */
|
|
90
|
+
project?: string;
|
|
67
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the per-project provisioned-team roster from the OFFLINE generated file
|
|
94
|
+
* (ND-2), falling back to the builtin roster when the file is unreadable or does
|
|
95
|
+
* not list the project. This is deliberately OFFLINE-SAFE: it never makes a
|
|
96
|
+
* session-start HTTP call to the coordination service (the shared SPOF).
|
|
97
|
+
*/
|
|
98
|
+
export declare function resolveRoster(project: string, env: NodeJS.ProcessEnv, readFileText: (path: string) => string | undefined): readonly string[];
|
|
68
99
|
/**
|
|
69
100
|
* Resolve the coordination token, self-provisioning from Key Vault when none is
|
|
70
101
|
* configured. Never throws — every failure is a typed result the caller acts on.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coordination-token.d.ts","sourceRoot":"","sources":["../../src/lib/coordination-token.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"coordination-token.d.ts","sourceRoot":"","sources":["../../src/lib/coordination-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AASH;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,0GASf,CAAC;AAMX,eAAO,MAAM,aAAa,0BAA0B,CAAC;AAErD,qFAAqF;AACrF,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAM1D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAElE;AAYD,MAAM,MAAM,kBAAkB;AAC5B,sFAAsF;AACpF,oBAAoB;AACtB,iFAAiF;GAC/E,UAAU;AACZ,8EAA8E;GAC5E,eAAe;AACjB,+EAA+E;GAC7E,eAAe,CAAC;AAEpB,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;IACjD,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACxE,oFAAoF;IACpF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACpD,uEAAuE;IACvE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA8CD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACjD,SAAS,MAAM,EAAE,CAkBnB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,GAAE,gBAAqB,GAAG,kBAAkB,CAqExF"}
|
|
@@ -4,36 +4,49 @@
|
|
|
4
4
|
* Ports the security-reviewed KV-fetch logic from the agent-config hook
|
|
5
5
|
* `coordination-brief.sh` into the CLI so that a bare `mx-agent coordination
|
|
6
6
|
* brief` (and drain/escalate/history) resolves its own token when none is
|
|
7
|
-
* configured
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* configured. MULTI-TENANT (Phase 1): identity is (project, team). `project`
|
|
8
|
+
* comes from resolveProductSlug() (config-derived, default 'memnexus'); `team`
|
|
9
|
+
* from the authoritative worktree marker. The isolation properties the security
|
|
10
|
+
* review required are preserved 1:1, now project-aware:
|
|
10
11
|
*
|
|
11
12
|
* - MF-2 The team slug is read from the authoritative worktree marker
|
|
12
13
|
* (.mx-coordination-team). It is NEVER derived from CLAUDE_TEAM_NAME
|
|
13
|
-
* or the worktree name.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* or the worktree name. The PROJECT is the committed project-config
|
|
15
|
+
* signal (resolveProductSlug), never a queue-mode client assertion.
|
|
16
|
+
* - SF-2 The (project, team) is validated against the per-project roster
|
|
17
|
+
* BEFORE any `az` call. A non-member fails CLOSED (no fetch).
|
|
18
|
+
* - MF-1 The token cache filename embeds BOTH slugs
|
|
19
|
+
* (.mx-coordination-token--<project>--<team>), per worktree. A worktree
|
|
20
|
+
* recycled to a different team/project can never read another tenant's
|
|
21
|
+
* cached token.
|
|
19
22
|
* - SF-3 The cache is created born 0600 (mode on create — no readable window).
|
|
20
|
-
* - No shell: the roster-validated
|
|
23
|
+
* - No shell: the roster-validated slugs are passed to `az` via execFile with an
|
|
21
24
|
* args array, never interpolated into a shell string.
|
|
22
|
-
* - The token is NEVER written to the global ~/.mx-agent config
|
|
23
|
-
* across worktrees — the MF-2 defect) and is NEVER logged.
|
|
25
|
+
* - The token is NEVER written to the global ~/.mx-agent config and NEVER logged.
|
|
24
26
|
* - Fail-OPEN on infra errors (no marker, az not logged in, KV unreachable):
|
|
25
27
|
* report "unprovisioned" so the caller degrades, never crash.
|
|
26
28
|
* - Fail-CLOSED only on a roster mismatch.
|
|
29
|
+
*
|
|
30
|
+
* ZERO-ORPHAN KV shim (Phase 1): the secret name is the parse-safe double-hyphen
|
|
31
|
+
* `coordination-token--<project>--<team>` (ND-1). For the DEFAULT project
|
|
32
|
+
* (memnexus) the live Key Vault still holds the PRE-migration single-hyphen
|
|
33
|
+
* `coordination-token-<team>` secrets, so the fetch falls back to the legacy name
|
|
34
|
+
* when the double-hyphen name is absent (the PRD migration grace-window dual-read).
|
|
35
|
+
* This keeps memnexus session-start working IDENTICALLY against the un-migrated
|
|
36
|
+
* vault; Phase 2 renames the secrets and the fallback is removed.
|
|
27
37
|
*/
|
|
28
38
|
import { execFileSync } from 'node:child_process';
|
|
29
39
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
30
40
|
import { join } from 'node:path';
|
|
31
41
|
import { getConfigValue } from './config.js';
|
|
32
42
|
import { CONFIG_KEY_TOKEN } from './coordination-client.js';
|
|
43
|
+
import { resolveProductSlug, DEFAULT_PRODUCT_SLUG } from './coordination-memory.js';
|
|
33
44
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
45
|
+
* BUILTIN memnexus provisioned-team roster — the OFFLINE fallback used only when
|
|
46
|
+
* the generated roster file cannot be read. Mirrors PROVISIONED_ROSTERS in
|
|
47
|
+
* coordination-service/src/config/rosters.ts. Keeping it here preserves
|
|
48
|
+
* offline-safety (a missing file never breaks the memnexus gate) without a live
|
|
49
|
+
* HTTP dependency. Aliases mx-agent / mx-agent-system are intentionally excluded.
|
|
37
50
|
*/
|
|
38
51
|
export const COORD_ROSTER = [
|
|
39
52
|
'platform',
|
|
@@ -45,9 +58,31 @@ export const COORD_ROSTER = [
|
|
|
45
58
|
'eval',
|
|
46
59
|
'product',
|
|
47
60
|
];
|
|
61
|
+
const BUILTIN_ROSTERS = {
|
|
62
|
+
[DEFAULT_PRODUCT_SLUG]: COORD_ROSTER,
|
|
63
|
+
};
|
|
48
64
|
export const KEYVAULT_NAME = 'mx-dev-eastus2-kv-001';
|
|
65
|
+
/** Env override for the offline roster file path (test seam / operator override). */
|
|
66
|
+
export const ENV_KEY_ROSTER_FILE = 'MX_COORD_ROSTER_FILE';
|
|
67
|
+
/** Repo-relative location of the committed generated offline roster file (ND-2). */
|
|
68
|
+
const ROSTER_FILE_REL = join('mx-agent-system', 'agent-config', 'coordination-rosters.generated.json');
|
|
49
69
|
const MARKER_FILE = '.mx-coordination-team';
|
|
50
|
-
|
|
70
|
+
/**
|
|
71
|
+
* ND-1 single-source KV secret name shape — reproduced here (the CLI is a
|
|
72
|
+
* separate package from coordination-service). MUST stay byte-identical to
|
|
73
|
+
* kvSecretName() in coordination-service/src/types.ts.
|
|
74
|
+
*/
|
|
75
|
+
export function kvSecretName(project, team) {
|
|
76
|
+
return `coordination-token--${project}--${team}`;
|
|
77
|
+
}
|
|
78
|
+
/** Legacy (pre-migration) single-hyphen name — memnexus dual-read fallback only. */
|
|
79
|
+
function legacyKvSecretName(team) {
|
|
80
|
+
return `coordination-token-${team}`;
|
|
81
|
+
}
|
|
82
|
+
/** Per-worktree token cache filename, embedding BOTH slugs (MF-1). */
|
|
83
|
+
function cacheFileName(project, team) {
|
|
84
|
+
return `.mx-coordination-token--${project}--${team}`;
|
|
85
|
+
}
|
|
51
86
|
/** Default marker/cache reader: undefined on missing or unreadable file. */
|
|
52
87
|
function defaultReadFileText(path) {
|
|
53
88
|
try {
|
|
@@ -60,11 +95,9 @@ function defaultReadFileText(path) {
|
|
|
60
95
|
}
|
|
61
96
|
}
|
|
62
97
|
/**
|
|
63
|
-
* Default KV fetch. The
|
|
64
|
-
* caller;
|
|
65
|
-
*
|
|
66
|
-
* gate were bypassed. Any failure (az not logged in, KV unreachable, missing
|
|
67
|
-
* secret) returns undefined → fail-open.
|
|
98
|
+
* Default KV fetch. The slugs embedded in `secretName` are roster-validated by
|
|
99
|
+
* the caller; the name is passed to `az` as a single argv element via execFile
|
|
100
|
+
* (no shell). Any failure returns undefined → fail-open.
|
|
68
101
|
*/
|
|
69
102
|
function defaultFetchSecret(vault, secretName) {
|
|
70
103
|
try {
|
|
@@ -79,9 +112,6 @@ function defaultFetchSecret(vault, secretName) {
|
|
|
79
112
|
/** SF-3 born-0600 cache write. A write failure is tolerated (fail-open). */
|
|
80
113
|
function defaultWriteCache(path, value) {
|
|
81
114
|
try {
|
|
82
|
-
// mode 0600 on create — 0600 has no group/other bits, so no umask can widen
|
|
83
|
-
// it. The cache is only written when absent (see resolveCoordinationToken),
|
|
84
|
-
// so this is always a create.
|
|
85
115
|
writeFileSync(path, value, { mode: 0o600 });
|
|
86
116
|
}
|
|
87
117
|
catch {
|
|
@@ -93,6 +123,36 @@ function firstLineNoSpace(raw) {
|
|
|
93
123
|
const firstLine = raw.split(/\r?\n/, 1)[0] ?? '';
|
|
94
124
|
return firstLine.replace(/\s+/g, '');
|
|
95
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve the per-project provisioned-team roster from the OFFLINE generated file
|
|
128
|
+
* (ND-2), falling back to the builtin roster when the file is unreadable or does
|
|
129
|
+
* not list the project. This is deliberately OFFLINE-SAFE: it never makes a
|
|
130
|
+
* session-start HTTP call to the coordination service (the shared SPOF).
|
|
131
|
+
*/
|
|
132
|
+
export function resolveRoster(project, env, readFileText) {
|
|
133
|
+
const candidates = [];
|
|
134
|
+
const override = env[ENV_KEY_ROSTER_FILE];
|
|
135
|
+
if (override)
|
|
136
|
+
candidates.push(override);
|
|
137
|
+
const worktree = env.CLAUDE_WORKTREE_PATH;
|
|
138
|
+
if (worktree)
|
|
139
|
+
candidates.push(join(worktree, ROSTER_FILE_REL));
|
|
140
|
+
for (const path of candidates) {
|
|
141
|
+
const raw = readFileText(path);
|
|
142
|
+
if (!raw)
|
|
143
|
+
continue;
|
|
144
|
+
try {
|
|
145
|
+
const parsed = JSON.parse(raw);
|
|
146
|
+
const teams = parsed.projects?.[project];
|
|
147
|
+
if (Array.isArray(teams))
|
|
148
|
+
return teams;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
/* malformed file → fall through to the builtin fallback (offline-safe) */
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return BUILTIN_ROSTERS[project] ?? [];
|
|
155
|
+
}
|
|
96
156
|
/**
|
|
97
157
|
* Resolve the coordination token, self-provisioning from Key Vault when none is
|
|
98
158
|
* configured. Never throws — every failure is a typed result the caller acts on.
|
|
@@ -103,15 +163,9 @@ export function resolveCoordinationToken(deps = {}) {
|
|
|
103
163
|
const env = deps.env ?? process.env;
|
|
104
164
|
const readFileText = deps.readFileText ?? defaultReadFileText;
|
|
105
165
|
// 1. Already configured → use it, skip KV. Env preferred, then global config.
|
|
106
|
-
// This keeps the hook path (which injects MX_COORD_SERVICE_TOKEN) and any
|
|
107
|
-
// manual `mx-agent config set coordination-service-token` working unchanged.
|
|
108
166
|
const envToken = env.MX_COORD_SERVICE_TOKEN;
|
|
109
167
|
if (envToken)
|
|
110
168
|
return { status: 'already-configured', token: envToken, source: 'env' };
|
|
111
|
-
// Belt-and-suspenders for the SF-A never-crash contract: getConfigValue →
|
|
112
|
-
// loadConfig → getConfigDir calls mkdirSync, which throws on an unwritable/
|
|
113
|
-
// read-only HOME (EACCES/EROFS/ENOSPC). Swallow it and treat the config token
|
|
114
|
-
// as absent so resolution continues to the marker/KV path instead of throwing.
|
|
115
169
|
let configToken;
|
|
116
170
|
try {
|
|
117
171
|
configToken = getConfigValue(CONFIG_KEY_TOKEN);
|
|
@@ -121,41 +175,48 @@ export function resolveCoordinationToken(deps = {}) {
|
|
|
121
175
|
}
|
|
122
176
|
if (configToken)
|
|
123
177
|
return { status: 'already-configured', token: configToken, source: 'config' };
|
|
124
|
-
// 2.
|
|
125
|
-
|
|
178
|
+
// 2. Resolve the PROJECT (config-derived, default memnexus). Never throws.
|
|
179
|
+
const project = deps.project ?? resolveProductSlug();
|
|
180
|
+
// 3. MF-2: read the authoritative team marker. No worktree/marker → fail-OPEN.
|
|
126
181
|
const worktree = env.CLAUDE_WORKTREE_PATH;
|
|
127
182
|
if (!worktree) {
|
|
128
|
-
return { status: 'unprovisioned', detail: 'CLAUDE_WORKTREE_PATH is unset — cannot locate the coordination marker' };
|
|
183
|
+
return { status: 'unprovisioned', project, detail: 'CLAUDE_WORKTREE_PATH is unset — cannot locate the coordination marker' };
|
|
129
184
|
}
|
|
130
185
|
const markerRaw = readFileText(join(worktree, MARKER_FILE));
|
|
131
186
|
if (markerRaw === undefined) {
|
|
132
|
-
return { status: 'unprovisioned', detail: 'no coordination marker (.mx-coordination-team) in this worktree' };
|
|
187
|
+
return { status: 'unprovisioned', project, detail: 'no coordination marker (.mx-coordination-team) in this worktree' };
|
|
133
188
|
}
|
|
134
189
|
const slug = firstLineNoSpace(markerRaw);
|
|
135
190
|
if (!slug) {
|
|
136
|
-
return { status: 'unprovisioned', detail: 'coordination marker is empty' };
|
|
191
|
+
return { status: 'unprovisioned', project, detail: 'coordination marker is empty' };
|
|
137
192
|
}
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
193
|
+
// 4. SF-2: roster-membership gate for THIS project BEFORE any az call. The
|
|
194
|
+
// roster comes from the OFFLINE file (single source), builtin fallback.
|
|
195
|
+
const roster = resolveRoster(project, env, readFileText);
|
|
196
|
+
if (!roster.includes(slug)) {
|
|
197
|
+
return { status: 'not-on-roster', project, slug };
|
|
141
198
|
}
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
// prior team's token — its cache path simply does not exist yet.
|
|
145
|
-
const cachePath = join(worktree, `${CACHE_PREFIX}${slug}`);
|
|
199
|
+
// 5. MF-1: (project, team)-bound per-worktree cache.
|
|
200
|
+
const cachePath = join(worktree, cacheFileName(project, slug));
|
|
146
201
|
const cachedRaw = readFileText(cachePath);
|
|
147
202
|
if (cachedRaw) {
|
|
148
203
|
const cached = firstLineNoSpace(cachedRaw);
|
|
149
204
|
if (cached)
|
|
150
|
-
return { status: 'resolved', token: cached, slug, source: 'cache' };
|
|
205
|
+
return { status: 'resolved', token: cached, project, slug, source: 'cache' };
|
|
151
206
|
}
|
|
152
|
-
//
|
|
153
|
-
//
|
|
207
|
+
// 6. Fetch from Key Vault. Primary: parse-safe double-hyphen name (ND-1).
|
|
208
|
+
// Fallback (memnexus only): legacy single-hyphen name — the zero-orphan
|
|
209
|
+
// dual-read against the un-migrated live vault (Phase 2 removes it).
|
|
154
210
|
const fetchSecret = deps.fetchSecret ?? defaultFetchSecret;
|
|
155
|
-
|
|
211
|
+
let fetched = fetchSecret(KEYVAULT_NAME, kvSecretName(project, slug));
|
|
212
|
+
let source = 'keyvault';
|
|
213
|
+
if (!fetched && project === DEFAULT_PRODUCT_SLUG) {
|
|
214
|
+
fetched = fetchSecret(KEYVAULT_NAME, legacyKvSecretName(slug));
|
|
215
|
+
}
|
|
156
216
|
if (!fetched) {
|
|
157
217
|
return {
|
|
158
218
|
status: 'unprovisioned',
|
|
219
|
+
project,
|
|
159
220
|
slug,
|
|
160
221
|
detail: 'Key Vault returned no token (az not logged in, KV unreachable, or secret missing)',
|
|
161
222
|
};
|
|
@@ -163,6 +224,6 @@ export function resolveCoordinationToken(deps = {}) {
|
|
|
163
224
|
// SF-3: cache born 0600. NEVER persisted to the global config.
|
|
164
225
|
const writeCache = deps.writeCache ?? defaultWriteCache;
|
|
165
226
|
writeCache(cachePath, fetched);
|
|
166
|
-
return { status: 'resolved', token: fetched, slug, source
|
|
227
|
+
return { status: 'resolved', token: fetched, project, slug, source };
|
|
167
228
|
}
|
|
168
229
|
//# sourceMappingURL=coordination-token.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coordination-token.js","sourceRoot":"","sources":["../../src/lib/coordination-token.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"coordination-token.js","sourceRoot":"","sources":["../../src/lib/coordination-token.ts"],"names":[],"mappings":"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"}
|