@asc-agent/runtime 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +22 -0
- package/dist/adapters/claude-code/binding.d.ts +5 -0
- package/dist/adapters/claude-code/binding.js +14 -0
- package/dist/adapters/claude-code/guard.d.ts +54 -0
- package/dist/adapters/claude-code/guard.js +295 -0
- package/dist/adapters/claude-code/install.d.ts +67 -0
- package/dist/adapters/claude-code/install.js +231 -0
- package/dist/adapters/claude-code/observer.d.ts +38 -0
- package/dist/adapters/claude-code/observer.js +77 -0
- package/dist/adapters/claude-code/probe.d.ts +53 -0
- package/dist/adapters/claude-code/probe.js +129 -0
- package/dist/adapters/claude-code/skill.d.ts +8 -0
- package/dist/adapters/claude-code/skill.js +267 -0
- package/dist/adapters/fixture-work/index.d.ts +34 -0
- package/dist/adapters/fixture-work/index.js +78 -0
- package/dist/adapters/github/adapter.d.ts +32 -0
- package/dist/adapters/github/adapter.js +95 -0
- package/dist/adapters/github/client.d.ts +30 -0
- package/dist/adapters/github/client.js +90 -0
- package/dist/adapters/github/context.d.ts +44 -0
- package/dist/adapters/github/context.js +170 -0
- package/dist/adapters/github/event-source.d.ts +55 -0
- package/dist/adapters/github/event-source.js +183 -0
- package/dist/adapters/github/scm.d.ts +50 -0
- package/dist/adapters/github/scm.js +104 -0
- package/dist/adapters/gitlab/adapter.d.ts +36 -0
- package/dist/adapters/gitlab/adapter.js +117 -0
- package/dist/adapters/gitlab/client.d.ts +30 -0
- package/dist/adapters/gitlab/client.js +47 -0
- package/dist/adapters/gitlab/ports.d.ts +40 -0
- package/dist/adapters/gitlab/ports.js +186 -0
- package/dist/adapters/jam/adapter.d.ts +53 -0
- package/dist/adapters/jam/adapter.js +144 -0
- package/dist/adapters/jam/event-source.d.ts +28 -0
- package/dist/adapters/jam/event-source.js +65 -0
- package/dist/adapters/jam/mcp-client.d.ts +50 -0
- package/dist/adapters/jam/mcp-client.js +218 -0
- package/dist/adapters/jam/ports.d.ts +51 -0
- package/dist/adapters/jam/ports.js +167 -0
- package/dist/adapters/local/identity.d.ts +27 -0
- package/dist/adapters/local/identity.js +40 -0
- package/dist/adapters/local/presentation.d.ts +14 -0
- package/dist/adapters/local/presentation.js +42 -0
- package/dist/adapters/markdown/layout.d.ts +15 -0
- package/dist/adapters/markdown/layout.js +49 -0
- package/dist/adapters/markdown/serialize.d.ts +9 -0
- package/dist/adapters/markdown/serialize.js +120 -0
- package/dist/adapters/markdown/state-store.d.ts +23 -0
- package/dist/adapters/markdown/state-store.js +316 -0
- package/dist/adapters/mattermost/client.d.ts +23 -0
- package/dist/adapters/mattermost/client.js +54 -0
- package/dist/adapters/mattermost/presentation.d.ts +43 -0
- package/dist/adapters/mattermost/presentation.js +88 -0
- package/dist/adapters/memory/mocks.d.ts +87 -0
- package/dist/adapters/memory/mocks.js +163 -0
- package/dist/adapters/memory/runtime-binding.d.ts +23 -0
- package/dist/adapters/memory/runtime-binding.js +118 -0
- package/dist/adapters/memory/state-store.d.ts +17 -0
- package/dist/adapters/memory/state-store.js +125 -0
- package/dist/adapters/text/renderer.d.ts +7 -0
- package/dist/adapters/text/renderer.js +81 -0
- package/dist/adapters/webhook/ingress.d.ts +103 -0
- package/dist/adapters/webhook/ingress.js +150 -0
- package/dist/cli/asc.d.ts +16 -0
- package/dist/cli/asc.js +2914 -0
- package/dist/cli/identity-config.d.ts +7 -0
- package/dist/cli/identity-config.js +31 -0
- package/dist/composition/observe.d.ts +21 -0
- package/dist/composition/observe.js +72 -0
- package/dist/composition/registry.d.ts +33 -0
- package/dist/composition/registry.js +62 -0
- package/dist/composition/runtime.d.ts +63 -0
- package/dist/composition/runtime.js +155 -0
- package/dist/core/approval/service.d.ts +17 -0
- package/dist/core/approval/service.js +129 -0
- package/dist/core/attach/bootstrap.d.ts +102 -0
- package/dist/core/attach/bootstrap.js +178 -0
- package/dist/core/attach/init.d.ts +29 -0
- package/dist/core/attach/init.js +100 -0
- package/dist/core/attach/setup-plan.d.ts +125 -0
- package/dist/core/attach/setup-plan.js +177 -0
- package/dist/core/attach/setup.d.ts +40 -0
- package/dist/core/attach/setup.js +140 -0
- package/dist/core/binding/types.d.ts +93 -0
- package/dist/core/binding/types.js +74 -0
- package/dist/core/distribution/release.d.ts +15 -0
- package/dist/core/distribution/release.js +27 -0
- package/dist/core/distribution/runtime-install.d.ts +50 -0
- package/dist/core/distribution/runtime-install.js +90 -0
- package/dist/core/distribution/runtime-select.d.ts +74 -0
- package/dist/core/distribution/runtime-select.js +149 -0
- package/dist/core/execution/executor.d.ts +51 -0
- package/dist/core/execution/executor.js +106 -0
- package/dist/core/execution/grant.d.ts +42 -0
- package/dist/core/execution/grant.js +78 -0
- package/dist/core/model/entities.d.ts +872 -0
- package/dist/core/model/entities.js +285 -0
- package/dist/core/model/ids.d.ts +41 -0
- package/dist/core/model/ids.js +50 -0
- package/dist/core/model/transitions.d.ts +26 -0
- package/dist/core/model/transitions.js +124 -0
- package/dist/core/monitor/coverage.d.ts +111 -0
- package/dist/core/monitor/coverage.js +129 -0
- package/dist/core/monitor/engine.d.ts +134 -0
- package/dist/core/monitor/engine.js +574 -0
- package/dist/core/monitor/health-alerts.d.ts +35 -0
- package/dist/core/monitor/health-alerts.js +88 -0
- package/dist/core/monitor/investigation.d.ts +82 -0
- package/dist/core/monitor/investigation.js +232 -0
- package/dist/core/monitor/observation.d.ts +79 -0
- package/dist/core/monitor/observation.js +110 -0
- package/dist/core/monitor/relevance.d.ts +47 -0
- package/dist/core/monitor/relevance.js +100 -0
- package/dist/core/monitor/signals.d.ts +59 -0
- package/dist/core/monitor/signals.js +105 -0
- package/dist/core/operator/local-operator.d.ts +60 -0
- package/dist/core/operator/local-operator.js +82 -0
- package/dist/core/operator/preflight.d.ts +60 -0
- package/dist/core/operator/preflight.js +139 -0
- package/dist/core/operator/proceed.d.ts +98 -0
- package/dist/core/operator/proceed.js +167 -0
- package/dist/core/operator/progress.d.ts +124 -0
- package/dist/core/operator/progress.js +135 -0
- package/dist/core/operator/render.d.ts +34 -0
- package/dist/core/operator/render.js +170 -0
- package/dist/core/operator/runtime-binding.d.ts +61 -0
- package/dist/core/operator/runtime-binding.js +22 -0
- package/dist/core/policy/ownership.d.ts +44 -0
- package/dist/core/policy/ownership.js +46 -0
- package/dist/core/policy/policy.d.ts +99 -0
- package/dist/core/policy/policy.js +147 -0
- package/dist/core/policy/remote-freeze.d.ts +101 -0
- package/dist/core/policy/remote-freeze.js +151 -0
- package/dist/core/policy/scope.d.ts +23 -0
- package/dist/core/policy/scope.js +93 -0
- package/dist/core/presentation/digest.d.ts +98 -0
- package/dist/core/presentation/digest.js +160 -0
- package/dist/core/resolver/load.d.ts +121 -0
- package/dist/core/resolver/load.js +246 -0
- package/dist/core/resolver/render.d.ts +4 -0
- package/dist/core/resolver/render.js +62 -0
- package/dist/core/resolver/resolve.d.ts +42 -0
- package/dist/core/resolver/resolve.js +51 -0
- package/dist/core/resolver/version.d.ts +22 -0
- package/dist/core/resolver/version.js +49 -0
- package/dist/core/runtime/audit.d.ts +374 -0
- package/dist/core/runtime/audit.js +454 -0
- package/dist/core/runtime/claims.d.ts +115 -0
- package/dist/core/runtime/claims.js +153 -0
- package/dist/core/runtime/closure.d.ts +73 -0
- package/dist/core/runtime/closure.js +162 -0
- package/dist/core/runtime/controller.d.ts +40 -0
- package/dist/core/runtime/controller.js +121 -0
- package/dist/core/runtime/escalation.d.ts +188 -0
- package/dist/core/runtime/escalation.js +322 -0
- package/dist/core/runtime/execution-state.d.ts +43 -0
- package/dist/core/runtime/execution-state.js +81 -0
- package/dist/core/runtime/front.d.ts +95 -0
- package/dist/core/runtime/front.js +144 -0
- package/dist/core/runtime/orchestrator.d.ts +54 -0
- package/dist/core/runtime/orchestrator.js +98 -0
- package/dist/core/runtime/query.d.ts +184 -0
- package/dist/core/runtime/query.js +213 -0
- package/dist/core/runtime/report.d.ts +33 -0
- package/dist/core/runtime/report.js +108 -0
- package/dist/core/runtime/session.d.ts +156 -0
- package/dist/core/runtime/session.js +281 -0
- package/dist/core/runtime/store-ops.d.ts +22 -0
- package/dist/core/runtime/store-ops.js +26 -0
- package/dist/core/view/build-view.d.ts +36 -0
- package/dist/core/view/build-view.js +131 -0
- package/dist/core/view/decision-view.d.ts +564 -0
- package/dist/core/view/decision-view.js +103 -0
- package/dist/core/workspace/identity.d.ts +82 -0
- package/dist/core/workspace/identity.js +133 -0
- package/dist/core/workspace/index-store.d.ts +140 -0
- package/dist/core/workspace/index-store.js +125 -0
- package/dist/core/workspace/migrate.d.ts +73 -0
- package/dist/core/workspace/migrate.js +123 -0
- package/dist/core/workspace/resolve.d.ts +34 -0
- package/dist/core/workspace/resolve.js +89 -0
- package/dist/ports/adapter.d.ts +35 -0
- package/dist/ports/adapter.js +12 -0
- package/dist/ports/approval.d.ts +98 -0
- package/dist/ports/approval.js +7 -0
- package/dist/ports/change-context.d.ts +22 -0
- package/dist/ports/change-context.js +8 -0
- package/dist/ports/event-source.d.ts +42 -0
- package/dist/ports/event-source.js +6 -0
- package/dist/ports/inventory.d.ts +53 -0
- package/dist/ports/inventory.js +12 -0
- package/dist/ports/presentation.d.ts +62 -0
- package/dist/ports/presentation.js +13 -0
- package/dist/ports/renderer.d.ts +23 -0
- package/dist/ports/renderer.js +7 -0
- package/dist/ports/resource-context.d.ts +51 -0
- package/dist/ports/resource-context.js +9 -0
- package/dist/ports/scm.d.ts +40 -0
- package/dist/ports/scm.js +6 -0
- package/dist/ports/state-store.d.ts +97 -0
- package/dist/ports/state-store.js +12 -0
- package/dist/presets/balanced.json +8 -0
- package/dist/presets/conservative.json +9 -0
- package/dist/presets/lightweight.json +8 -0
- package/dist/profiles/example-team/profile.json +101 -0
- package/dist/profiles/pilot-local/profile.json +56 -0
- package/dist/schemas/profile.d.ts +631 -0
- package/dist/schemas/profile.js +287 -0
- package/package.json +55 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type WorkspaceIndex } from './index-store.ts';
|
|
2
|
+
export declare const ASC_DIR = ".asc";
|
|
3
|
+
export type Resolution = {
|
|
4
|
+
kind: 'EXPLICIT';
|
|
5
|
+
root: string;
|
|
6
|
+
}
|
|
7
|
+
/** 이 기계에 등록된 workspace. 경로가 바뀌어도 따라온다. */
|
|
8
|
+
| {
|
|
9
|
+
kind: 'REGISTERED';
|
|
10
|
+
root: string;
|
|
11
|
+
workspaceId: string;
|
|
12
|
+
locator: string;
|
|
13
|
+
}
|
|
14
|
+
/** 저장소 안의 `.asc/` — 팀이 채택했거나 아직 이전하지 않은 개인 legacy다. */
|
|
15
|
+
| {
|
|
16
|
+
kind: 'PROJECT_LOCAL';
|
|
17
|
+
root: string;
|
|
18
|
+
projectRoot: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'UNRESOLVED';
|
|
21
|
+
detail: string;
|
|
22
|
+
};
|
|
23
|
+
export type ResolveInput = {
|
|
24
|
+
cwd: string;
|
|
25
|
+
/** `--root` 로 사람이 지정한 값. 있으면 무조건 이긴다. */
|
|
26
|
+
explicitRoot?: string;
|
|
27
|
+
index?: WorkspaceIndex;
|
|
28
|
+
/** 여기를 넘어서는 위로 올라가지 않는다. 보통 사용자 홈. */
|
|
29
|
+
stopAt?: string;
|
|
30
|
+
exists?: (path: string) => Promise<boolean>;
|
|
31
|
+
};
|
|
32
|
+
export declare function resolveWorkspace(input: ResolveInput): Promise<Resolution>;
|
|
33
|
+
/** 사람이 읽는 한 줄. 왜 그 뿌리인지가 함께 와야 사람이 틀린 결합을 알아챈다. */
|
|
34
|
+
export declare function resolutionLine(resolution: Resolution): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Workspace Resolution — "지금 여기는 어느 ASC runtime인가"를 정하는 **한 곳** (C-11 §3·B-45).
|
|
2
|
+
//
|
|
3
|
+
// 지금까지 이 질문의 답은 네 군데에 따로 있었다: CLI의 discoverRoot, attach의
|
|
4
|
+
// discoverProjectRoot, bootstrap의 내부 조립, 그리고 guard hook 안의 복제본.
|
|
5
|
+
// 넷이 조금씩 다르게 답하면 "어디는 되고 어디는 안 되는" 상태가 생기고, 실제로 그랬다
|
|
6
|
+
// (`--root` 가 host 명령에만 안 먹던 비대칭).
|
|
7
|
+
//
|
|
8
|
+
// 우선순위는 명시 > 등록 > 발견이다:
|
|
9
|
+
//
|
|
10
|
+
// 1. explicit root 사람이 말한 것이 이긴다
|
|
11
|
+
// 2. workspace index 이 기계에 등록된 locator (경로가 바뀌어도 따라온다)
|
|
12
|
+
// 3. project-adopted 저장소 안의 `.asc/` — 팀이 채택한 경우 (legacy 개인 사용 포함)
|
|
13
|
+
// 4. UNRESOLVED 모르면 모른다고 한다
|
|
14
|
+
//
|
|
15
|
+
// **3번의 탐색에는 경계가 있다.** 예전에는 파일시스템 루트까지 올라갔는데, 사용자 홈에
|
|
16
|
+
// `~/.asc` 가 생기는 순간 홈 아래 아무 저장소나 그 뿌리로 오인 매칭된다. 그래서 홈을
|
|
17
|
+
// 넘지 않고, 정지선(`stopAt`)을 지난 뒤에는 더 올라가지 않는다.
|
|
18
|
+
import { constants } from 'node:fs';
|
|
19
|
+
import { access } from 'node:fs/promises';
|
|
20
|
+
import { dirname, join, resolve } from 'node:path';
|
|
21
|
+
import { normalizeLocator } from "./identity.js";
|
|
22
|
+
import { lookupLocator } from "./index-store.js";
|
|
23
|
+
export const ASC_DIR = '.asc';
|
|
24
|
+
const defaultExists = async (path) => {
|
|
25
|
+
try {
|
|
26
|
+
await access(path, constants.F_OK);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
export async function resolveWorkspace(input) {
|
|
34
|
+
if (input.explicitRoot)
|
|
35
|
+
return { kind: 'EXPLICIT', root: input.explicitRoot };
|
|
36
|
+
const exists = input.exists ?? defaultExists;
|
|
37
|
+
if (input.index) {
|
|
38
|
+
const found = lookupLocator(input.index, input.cwd);
|
|
39
|
+
// 등록돼 있는데 뿌리가 없으면 **넘어가지 않는다.** 조용히 다음 후보로 가면
|
|
40
|
+
// 사라진 workspace 대신 엉뚱한 것에 붙는다 (C-11 §4 조건부 fail-closed와 같은 태도).
|
|
41
|
+
if (found) {
|
|
42
|
+
return (await exists(found.root))
|
|
43
|
+
? { kind: 'REGISTERED', root: found.root, workspaceId: found.workspaceId, locator: found.locator }
|
|
44
|
+
: {
|
|
45
|
+
kind: 'UNRESOLVED',
|
|
46
|
+
detail: `${found.workspaceId} 가 등록돼 있으나 runtime(${found.root})이 없다 — 옮겼거나 지워졌다`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const local = await findProjectLocal(input.cwd, input.stopAt, exists);
|
|
51
|
+
if (local)
|
|
52
|
+
return { kind: 'PROJECT_LOCAL', root: join(local, ASC_DIR), projectRoot: local };
|
|
53
|
+
return { kind: 'UNRESOLVED', detail: '이 경로에 붙은 ASC workspace가 없다' };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 저장소 안의 `.asc/` 를 위로 올라가며 찾되 **경계를 지킨다**.
|
|
57
|
+
*
|
|
58
|
+
* `stopAt`(보통 홈)을 포함해 그 위로는 보지 않는다. 홈에 `~/.asc` 가 있는 것은 정상이고,
|
|
59
|
+
* 그것을 프로젝트 상태로 읽으면 홈 아래 모든 저장소가 한 workspace가 된다.
|
|
60
|
+
*/
|
|
61
|
+
async function findProjectLocal(start, stopAt, exists) {
|
|
62
|
+
const boundary = stopAt ? normalizeLocator(resolve(stopAt)) : null;
|
|
63
|
+
let dir = resolve(start);
|
|
64
|
+
for (;;) {
|
|
65
|
+
const normalized = normalizeLocator(dir);
|
|
66
|
+
// 정지선 자체는 보지 않는다 — 홈의 `.asc` 는 user runtime이지 프로젝트 상태가 아니다
|
|
67
|
+
if (boundary && normalized === boundary)
|
|
68
|
+
return null;
|
|
69
|
+
if (await exists(join(dir, ASC_DIR)))
|
|
70
|
+
return dir;
|
|
71
|
+
const parent = dirname(dir);
|
|
72
|
+
if (parent === dir)
|
|
73
|
+
return null;
|
|
74
|
+
dir = parent;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** 사람이 읽는 한 줄. 왜 그 뿌리인지가 함께 와야 사람이 틀린 결합을 알아챈다. */
|
|
78
|
+
export function resolutionLine(resolution) {
|
|
79
|
+
switch (resolution.kind) {
|
|
80
|
+
case 'EXPLICIT':
|
|
81
|
+
return `runtime: ${resolution.root} (given with --root)`;
|
|
82
|
+
case 'REGISTERED':
|
|
83
|
+
return `runtime: ${resolution.root} (workspace ${resolution.workspaceId} · ${resolution.locator})`;
|
|
84
|
+
case 'PROJECT_LOCAL':
|
|
85
|
+
return `runtime: ${resolution.root} (.asc inside the repository — team-adopted, or personal state not yet migrated)`;
|
|
86
|
+
case 'UNRESOLVED':
|
|
87
|
+
return `no runtime — ${resolution.detail}`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AdapterDescriptor, BindingCandidate, ProbeState } from '../core/binding/types.ts';
|
|
2
|
+
/** discover가 훑는 환경. 파일·프로세스 접근을 호출자가 정해 준다 — adapter가 임의로 뒤지지 않는다. */
|
|
3
|
+
export type DiscoveryContext = {
|
|
4
|
+
/** 대상 프로젝트 뿌리. */
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
/** 환경변수. 자격 **존재 여부** 판단에만 쓰고 값을 실어 나르지 않는다. */
|
|
7
|
+
env?: NodeJS.ProcessEnv;
|
|
8
|
+
};
|
|
9
|
+
export type ProbeResult = {
|
|
10
|
+
state: ProbeState;
|
|
11
|
+
/** 상태만 주면 고칠 수가 없다. UNCONFIGURED면 무엇을 채워야 하는지 말한다. */
|
|
12
|
+
detail?: string;
|
|
13
|
+
/** 실측으로 확인된 제공 범위. describe보다 좁을 수 있다. */
|
|
14
|
+
provides?: readonly BindingCandidate['provides'][number][];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* adapter 자체가 지금 쓸 수 있는 상태인가 — **프로젝트와 무관한 사실**이다.
|
|
18
|
+
*
|
|
19
|
+
* 왜 binding probe와 나누는가: "도구는 깔려 있고 자격도 있다"와 "이 프로젝트가 그 도구에
|
|
20
|
+
* 연결돼 있다"는 다른 사실이다. 둘을 합치면 사람이 무엇을 해야 하는지 알 수 없다 —
|
|
21
|
+
* 도구를 설치할 일인지, 이 프로젝트를 붙일 일인지.
|
|
22
|
+
*
|
|
23
|
+
* 제공하지 않는 adapter는 그 구분이 필요 없다는 뜻이다.
|
|
24
|
+
*/
|
|
25
|
+
export type RuntimeStatus = {
|
|
26
|
+
state: ProbeState;
|
|
27
|
+
detail?: string;
|
|
28
|
+
};
|
|
29
|
+
export interface Adapter {
|
|
30
|
+
describe(): AdapterDescriptor;
|
|
31
|
+
/** 후보가 없으면 빈 배열. 그것도 사람이 알아야 할 사실이다. */
|
|
32
|
+
discover(context: DiscoveryContext): Promise<BindingCandidate[]>;
|
|
33
|
+
probe(candidate: BindingCandidate, context: DiscoveryContext): Promise<ProbeResult>;
|
|
34
|
+
runtime?(context: DiscoveryContext): Promise<RuntimeStatus>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Adapter Contract — 외부 시스템 하나를 ASC에 잇는 모듈의 공통 lifecycle (C-09 §5).
|
|
2
|
+
//
|
|
3
|
+
// 세 단계는 비용도 부작용도 다르다. 합치면 "설정을 안 한 것"과 "닿지 않는 것"이
|
|
4
|
+
// 구분되지 않고, 계획을 세우려고 부른 함수가 네트워크를 친다.
|
|
5
|
+
//
|
|
6
|
+
// describe() 정적 선언. 호출도 네트워크도 없다
|
|
7
|
+
// discover() 이 프로젝트·환경에서 연결 가능한 후보를 찾는다 (로컬 관찰)
|
|
8
|
+
// probe() 그 후보가 실제로 쓸 수 있는지 잰다 (외부 접촉 가능)
|
|
9
|
+
//
|
|
10
|
+
// adapter는 자기가 제공하는 Port만 구현한다. 전부 구현할 필요가 없다 —
|
|
11
|
+
// 작업 추적기가 변경 경로를 모르고, 전달 채널은 그 둘 다 모른다 (C-09 §2.1).
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ApprovalDecision } from '../core/model/entities.ts';
|
|
2
|
+
import type { DecisionView } from '../core/view/decision-view.ts';
|
|
3
|
+
/**
|
|
4
|
+
* 채널이 지원하는 표현 수단 (OM §11.4). Profile은 원하는 UX만 선언하고, Adapter는
|
|
5
|
+
* 지원하지 못하는 수단을 아래로 degrade한다:
|
|
6
|
+
* rich → interactive → text → Local Inbox.
|
|
7
|
+
*/
|
|
8
|
+
export type ApprovalCapability = 'interactive_actions' | 'rich_blocks' | 'dialogs' | 'ephemeral_feedback' | 'threads' | 'priority' | 'acknowledgement' | 'silent_notification';
|
|
9
|
+
/**
|
|
10
|
+
* 채널에 표시한 결과. Core는 이 값을 해석하지 않고 그대로 Adapter에게 돌려준다 —
|
|
11
|
+
* 외부 메시지 식별자(PresentationRecord)의 소유자는 Adapter다 (아래 주석 참조).
|
|
12
|
+
*/
|
|
13
|
+
export type PresentationOutcome = {
|
|
14
|
+
ok: true;
|
|
15
|
+
externalRef?: string;
|
|
16
|
+
}
|
|
17
|
+
/** best-effort 실패. canonical state에는 영향이 없어야 한다 (C-01 §9). */
|
|
18
|
+
| {
|
|
19
|
+
ok: false;
|
|
20
|
+
error: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* PresentationRecord(request ↔ 외부 표시물 매핑) 소유 결정 — B-02에서 확정.
|
|
24
|
+
*
|
|
25
|
+
* **Adapter-owned metadata로 둔다. Core Logical Entity가 아니다.**
|
|
26
|
+
* 근거:
|
|
27
|
+
* - Core가 채널별 매핑을 entity로 들면 채널이 늘 때마다 Core 스키마가 흔들린다.
|
|
28
|
+
* Core는 플랫폼을 몰라야 한다는 OM §11.3과 정면으로 어긋난다.
|
|
29
|
+
* - 매핑은 정본이 아니다 (C-01 §9). 정본 entity와 같은 저장·전이 규율을 줄 이유가 없다.
|
|
30
|
+
* - 표시 갱신은 best-effort다. 실패가 canonical state에 영향을 주지 않으려면 매핑이
|
|
31
|
+
* Core 전이 경로 밖에 있어야 한다.
|
|
32
|
+
* 저장 위치: StateStore.scope(adapterId)의 격리 공간 (ports/state-store.ts).
|
|
33
|
+
* Core가 하는 일은 상태가 바뀐 view를 채널들에 알리는 것까지이고, 어떤 메시지를
|
|
34
|
+
* 고칠지는 각 Adapter가 자기 매핑을 보고 정한다.
|
|
35
|
+
*/
|
|
36
|
+
export interface ApprovalChannel {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly capabilities: ReadonlySet<ApprovalCapability>;
|
|
39
|
+
/** 새 요청을 사람에게 보인다. */
|
|
40
|
+
present(view: DecisionView): Promise<PresentationOutcome>;
|
|
41
|
+
/**
|
|
42
|
+
* 상태가 바뀐 요청의 표현을 갱신한다 (다른 채널에서 결정됨 등).
|
|
43
|
+
* 실패해도 Core는 진행한다 — 낡은 버튼 입력은 어차피 CAS가 거절한다.
|
|
44
|
+
*/
|
|
45
|
+
update(view: DecisionView): Promise<PresentationOutcome>;
|
|
46
|
+
}
|
|
47
|
+
/** 결정 제출 결과. 사람에게 무엇이 일어났는지 설명할 수 있을 만큼 구체적이어야 한다. */
|
|
48
|
+
export type DecisionOutcome = {
|
|
49
|
+
ok: true;
|
|
50
|
+
view: DecisionView;
|
|
51
|
+
}
|
|
52
|
+
/** 다른 채널·다른 시점에 이미 결정됐다 (C-01 §7 STALE / ALREADY_DECIDED). */
|
|
53
|
+
| {
|
|
54
|
+
ok: false;
|
|
55
|
+
reason: 'ALREADY_DECIDED';
|
|
56
|
+
view: DecisionView;
|
|
57
|
+
}
|
|
58
|
+
/** 읽은 뒤 요청이 바뀌었다 — 다시 읽고 판단해야 한다. */
|
|
59
|
+
| {
|
|
60
|
+
ok: false;
|
|
61
|
+
reason: 'STALE';
|
|
62
|
+
view: DecisionView;
|
|
63
|
+
}
|
|
64
|
+
/** 승인 권한자가 아니다 (OM §11.6). 시도 자체를 History에 남긴다. */
|
|
65
|
+
| {
|
|
66
|
+
ok: false;
|
|
67
|
+
reason: 'FORBIDDEN_ACTOR';
|
|
68
|
+
} | {
|
|
69
|
+
ok: false;
|
|
70
|
+
reason: 'NOT_ALLOWED_DECISION';
|
|
71
|
+
} | {
|
|
72
|
+
ok: false;
|
|
73
|
+
reason: 'EXPIRED';
|
|
74
|
+
} | {
|
|
75
|
+
ok: false;
|
|
76
|
+
reason: 'NOT_FOUND';
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* 채널이 Core로 결정을 밀어 넣는 입구. 방향이 중요하다 — Core가 채널을 폴링하지 않고,
|
|
80
|
+
* 채널이 사람의 명시적 입력을 받았을 때만 호출한다.
|
|
81
|
+
* Agent는 요청을 읽을 수 있지만 이 함수를 자기 판단으로 호출할 수 없다 (C-01 §5).
|
|
82
|
+
*/
|
|
83
|
+
export interface DecisionSink {
|
|
84
|
+
submit(decision: ApprovalDecision): Promise<DecisionOutcome>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 결정 actor가 실제 승인 권한자인지 확인한다 (OM §11.6).
|
|
88
|
+
* Local이라는 이유로 무조건 신뢰하지 않는다 — Identity는 Credential이 아니며,
|
|
89
|
+
* Token은 Secret Store에 남고 여기에는 오지 않는다.
|
|
90
|
+
*/
|
|
91
|
+
export interface IdentityBinding {
|
|
92
|
+
/** channel에서 인증된 actor가 이 요청의 authorizedApprover와 같은 사람인가. */
|
|
93
|
+
verify(input: {
|
|
94
|
+
channel: string;
|
|
95
|
+
actor: string;
|
|
96
|
+
authorizedApprover: string;
|
|
97
|
+
}): Promise<boolean>;
|
|
98
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Approval Port — 승인 요청을 사람에게 보이고 사람의 결정을 돌려받는 경계.
|
|
2
|
+
// Core는 플랫폼 메시지 구조를 모른다 (OM §11.3). Messenger는 선택 구성요소이며,
|
|
3
|
+
// 채널이 하나도 없어도 Local 경로만으로 승인 lifecycle이 완결되어야 한다.
|
|
4
|
+
//
|
|
5
|
+
// 멀티채널은 여러 요청이 아니라 하나의 요청에 대한 여러 표현이다 (C-01 §7) —
|
|
6
|
+
// 모든 채널이 같은 requestId를 쓰고, 최초 유효 결정 이후의 입력은 CAS로 거절된다.
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type ChangeSummary = {
|
|
2
|
+
reference: string;
|
|
3
|
+
/**
|
|
4
|
+
* 바뀐 경로들. ASC scope 문법과 대조 가능한 실제 경로여야 한다 (패턴이 아니다).
|
|
5
|
+
* provider가 일부만 주면 `truncated`로 그 사실을 알린다 — 없는 것을 없다고 하면
|
|
6
|
+
* "내 영역은 안 바뀌었다"는 틀린 판정이 나온다.
|
|
7
|
+
*/
|
|
8
|
+
changedPaths: readonly string[];
|
|
9
|
+
truncated?: boolean;
|
|
10
|
+
/** 사람이 읽는 한두 줄. Core는 그대로 옮기고 요약하지 않는다. */
|
|
11
|
+
summary?: string;
|
|
12
|
+
/** 변경 묶음 식별자(커밋·리비전 등). adapter 소관 문자열. */
|
|
13
|
+
revisions?: readonly string[];
|
|
14
|
+
revisionMarker: string;
|
|
15
|
+
/** 검토·승인 상태. provider가 알려주는 만큼만. */
|
|
16
|
+
reviewState?: string;
|
|
17
|
+
missing?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export interface ChangeContextPort {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
getChange(reference: string): Promise<ChangeSummary>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Change Context Port — 무엇이 어디서 바뀌었는가 (C-09 §2.3).
|
|
2
|
+
//
|
|
3
|
+
// 이 Port가 따로 있는 이유는 relevance 판정 때문이다. "나를 불렀는가"가 아니라
|
|
4
|
+
// "내 영역이 바뀌었는가"로 관련성을 보려면 변경 경로를 실제로 읽어야 한다 (C-07 §3.2).
|
|
5
|
+
//
|
|
6
|
+
// diff 전문을 다루지 않는다. Core는 경로와 요약까지만 필요하고, 그 이상은 사람이
|
|
7
|
+
// provider에서 본다 — 전문을 끌어오면 조사 한 건이 통째로 비싸진다.
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EventType, Priority } from '../core/model/entities.ts';
|
|
2
|
+
/**
|
|
3
|
+
* 아직 분류되지 않은 원본 이벤트. eventKey는 provider가 만들고, dedupe는 log 훑기가
|
|
4
|
+
* 아니라 이 key의 exact lookup으로 한다 (OM §10.4).
|
|
5
|
+
*/
|
|
6
|
+
export type RawEvent = {
|
|
7
|
+
eventKey: string;
|
|
8
|
+
detectedAt: string;
|
|
9
|
+
reference: string;
|
|
10
|
+
/** Adapter가 아는 만큼의 힌트. 최종 분류·우선순위는 Monitor가 Profile 기준으로 정한다. */
|
|
11
|
+
hints?: {
|
|
12
|
+
type?: EventType;
|
|
13
|
+
priority?: Priority;
|
|
14
|
+
labels?: readonly string[];
|
|
15
|
+
actors?: readonly string[];
|
|
16
|
+
};
|
|
17
|
+
/** provider 원본. Phase B가 필요할 때만 들여다본다. */
|
|
18
|
+
raw?: unknown;
|
|
19
|
+
};
|
|
20
|
+
/** 다음 조회 시작점. 형태는 provider마다 다르므로 Core는 문자열로만 다룬다. */
|
|
21
|
+
export type Cursor = string | null;
|
|
22
|
+
export type EventBatch = {
|
|
23
|
+
events: RawEvent[];
|
|
24
|
+
cursor: Cursor;
|
|
25
|
+
/** 이번 회차에 더 남은 것이 있는가 — 폭주 시 나눠 가져오기 위함. */
|
|
26
|
+
hasMore?: boolean;
|
|
27
|
+
};
|
|
28
|
+
export interface EventSource {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
/**
|
|
31
|
+
* "지금부터 보겠다"는 뜻의 cursor. 처음 붙인 저장소의 과거를 통째로 긁으면 그 자체가
|
|
32
|
+
* 잡음이 되므로, 시작점을 정해 두고 출발할 수 있어야 한다 (OM §18).
|
|
33
|
+
* 구현하지 않은 Adapter는 늘 처음부터 본다.
|
|
34
|
+
*/
|
|
35
|
+
cursorFrom?(since: string): Cursor;
|
|
36
|
+
/**
|
|
37
|
+
* cursor 이후의 이벤트를 가져온다. 중간 실패 시 cursor를 전진시키지 않아도 되도록
|
|
38
|
+
* cursor 갱신 책임은 호출자(Monitor)에게 있다 — 누락보다 중복이 안전하고, 중복은
|
|
39
|
+
* dedupe가 거른다 (OM §10.5).
|
|
40
|
+
*/
|
|
41
|
+
drain(cursor: Cursor): Promise<EventBatch>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* provider가 아는 리소스 하나의 현재 모습. 필드는 **비교에 필요한 만큼만** 둔다 —
|
|
3
|
+
* 본문·댓글·변경 내용은 ResourceContext / ChangeContext의 몫이고, 여기에 끌어오면
|
|
4
|
+
* 전수 열거 한 번이 통째로 비싸진다.
|
|
5
|
+
*/
|
|
6
|
+
export type InventoryItem = {
|
|
7
|
+
/** provider-neutral 식별자. 문법은 adapter가 정하고 Core는 문자열로만 다룬다. */
|
|
8
|
+
reference: string;
|
|
9
|
+
/**
|
|
10
|
+
* provider의 상태 어휘를 그대로 둔다. Core는 이 값으로 분기하지 않고 비교만 한다 —
|
|
11
|
+
* 상태 이름을 Core가 해석하기 시작하면 provider 도메인이 Core로 올라온다.
|
|
12
|
+
*/
|
|
13
|
+
state: string;
|
|
14
|
+
updatedAt: string;
|
|
15
|
+
/**
|
|
16
|
+
* 실질 변화 마커 (C-07 §4). adapter가 만들고 **Core는 같은지 다른지만 본다.**
|
|
17
|
+
* 무엇을 넣을지는 adapter가 정한다 — 갱신 시각 하나로는 댓글 외의 변화를 놓친다.
|
|
18
|
+
*/
|
|
19
|
+
revisionMarker: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
assignees?: readonly string[];
|
|
22
|
+
labels?: readonly string[];
|
|
23
|
+
};
|
|
24
|
+
export type InventoryQuery = {
|
|
25
|
+
/**
|
|
26
|
+
* 이 시각 이후 변한 것만. 생략하면 전부 — Census가 그렇게 쓴다.
|
|
27
|
+
* 상태로 거르지 않는다는 것이 이 Port의 계약이므로 state 필터는 두지 않는다.
|
|
28
|
+
*/
|
|
29
|
+
updatedSince?: string;
|
|
30
|
+
/** adapter가 아는 리소스 갈래(이슈·변경요청·작업항목 등). 생략하면 adapter 기본값. */
|
|
31
|
+
kinds?: readonly string[];
|
|
32
|
+
};
|
|
33
|
+
export type InventoryPage = {
|
|
34
|
+
items: InventoryItem[];
|
|
35
|
+
/**
|
|
36
|
+
* 다음 페이지 시작점. 없으면 끝이다.
|
|
37
|
+
* 페이지를 다 돌지 못했는데 끝인 척하면 Census가 "사라졌다"를 잘못 만들어낸다.
|
|
38
|
+
*/
|
|
39
|
+
next?: string;
|
|
40
|
+
/**
|
|
41
|
+
* **마지막 페이지에서만 true가 될 수 있다** — "여기까지 오는 동안 빠짐이 없었다"는 뜻이다.
|
|
42
|
+
* 중간 페이지는 아직 알 수 없으므로 false다. 모르면 false다 (C-07 §8.2).
|
|
43
|
+
*
|
|
44
|
+
* 이 값이 false인 열거로 missing reference를 판정해서는 안 된다 — 페이지를 다 돌지
|
|
45
|
+
* 못한 목록으로 비교하면 멀쩡한 리소스가 사라졌다고 나온다.
|
|
46
|
+
*/
|
|
47
|
+
complete: boolean;
|
|
48
|
+
};
|
|
49
|
+
export interface InventoryPort {
|
|
50
|
+
readonly id: string;
|
|
51
|
+
/** 한 페이지씩. 전부 도는 것은 호출자의 몫이다 — 중단·재개 지점을 Core가 쥔다. */
|
|
52
|
+
enumerate(query: InventoryQuery, cursor?: string): Promise<InventoryPage>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Inventory Port — provider가 지금 들고 있는 목록을 상태 무관하게 열거한다 (C-09 §2).
|
|
2
|
+
//
|
|
3
|
+
// EventSource와 답하는 질문이 다르다:
|
|
4
|
+
// EventSource "그 사이에 무슨 일이 있었나" — 증분. 놓치면 그대로 놓친다
|
|
5
|
+
// Inventory "지금 무엇이 있나" — 전수. 놓친 것을 찾아낸다
|
|
6
|
+
//
|
|
7
|
+
// 그래서 **닫힌 것도 포함한다.** 닫힌 Issue에 새 댓글이 달리고, 병합된 변경에 논의가
|
|
8
|
+
// 이어지고, 끝난 작업의 설명이 고쳐진다. open만 세는 열거는 그 전부를 놓친다.
|
|
9
|
+
//
|
|
10
|
+
// 여기서 목록을 받아 무엇이 달라졌는지 판정하는 것은 Monitor의 몫이다 — 이 Port는
|
|
11
|
+
// 비교하지 않고, 우선순위를 매기지 않고, 무엇이 중요한지도 모른다.
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Priority } from '../core/model/entities.ts';
|
|
2
|
+
import type { DecisionSummary } from '../core/view/decision-view.ts';
|
|
3
|
+
/**
|
|
4
|
+
* 채널 제품이 아니라 **할 수 있는 일**로 정의한다 (C-08 §1.1). 이름을 채널로 잡으면
|
|
5
|
+
* 메신저가 아닌 전달 수단이 2급이 된다.
|
|
6
|
+
*/
|
|
7
|
+
export type PresentationCapability =
|
|
8
|
+
/** 묶음을 보여줄 수 있다. */
|
|
9
|
+
'presentation.digest'
|
|
10
|
+
/** 급한 것을 눈에 띄게 전달할 수 있다. */
|
|
11
|
+
| 'presentation.priority'
|
|
12
|
+
/** 그 자리에서 사람의 결정을 받을 수 있다 (ApprovalChannel 쪽 표면). */
|
|
13
|
+
| 'approval.interactive';
|
|
14
|
+
/** 묶음 한 덩어리. 무엇을 어떻게 그릴지는 adapter가 정한다. */
|
|
15
|
+
export type DigestBatch = {
|
|
16
|
+
/** 이 묶음을 만든 시각. 사람이 "언제 것"인지 알아야 한다. */
|
|
17
|
+
at: string;
|
|
18
|
+
/**
|
|
19
|
+
* 우선순위 구간별 항목. 전부 **같은 request의 또 하나의 표현**이며 새 request가 아니다
|
|
20
|
+
* (C-08 §3.1).
|
|
21
|
+
*/
|
|
22
|
+
groups: readonly {
|
|
23
|
+
priority: Priority;
|
|
24
|
+
items: readonly DecisionSummary[];
|
|
25
|
+
}[];
|
|
26
|
+
/**
|
|
27
|
+
* 보이지 않게 걸러진 것의 수. 숫자만 알려도 사람은 "무엇을 못 보고 있는지"를 안다 —
|
|
28
|
+
* 0으로 감추면 걸러졌다는 사실 자체가 사라진다.
|
|
29
|
+
*/
|
|
30
|
+
suppressed?: {
|
|
31
|
+
shadow: number;
|
|
32
|
+
alreadyDecided: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* 빠른 경로가 아니라 회수 경로에서 발견된 항목 수. coverage에 대한 정보이며
|
|
36
|
+
* 우선순위와 무관하다 (C-07 §1.6).
|
|
37
|
+
*/
|
|
38
|
+
recovered?: number;
|
|
39
|
+
};
|
|
40
|
+
export type DeliveryOutcome = {
|
|
41
|
+
ok: true;
|
|
42
|
+
externalRef?: string;
|
|
43
|
+
}
|
|
44
|
+
/** best-effort 실패. canonical state에 영향이 없어야 한다 (C-08 §1.3). */
|
|
45
|
+
| {
|
|
46
|
+
ok: false;
|
|
47
|
+
error: string;
|
|
48
|
+
};
|
|
49
|
+
export interface PresentationPort {
|
|
50
|
+
readonly id: string;
|
|
51
|
+
readonly capabilities: ReadonlySet<PresentationCapability>;
|
|
52
|
+
/** 묶음 전달. `presentation.digest`를 제공하는 adapter만 의미 있게 구현한다. */
|
|
53
|
+
presentDigest(batch: DigestBatch): Promise<DeliveryOutcome>;
|
|
54
|
+
/**
|
|
55
|
+
* 지금 끊어야 하는 한 건. `presentation.priority`가 없으면 호출자가 digest로 내린다 —
|
|
56
|
+
* 조용히 무시하지 않고 degrade한다.
|
|
57
|
+
*
|
|
58
|
+
* 요약을 받는다. 더 필요하면 채널이 `requestId`로 다시 읽는다 — Core가 전체 view를
|
|
59
|
+
* 만들어 넘기면 전달 한 번에 조사 한 번이 딸려 붙는다.
|
|
60
|
+
*/
|
|
61
|
+
presentUrgent?(item: DecisionSummary): Promise<DeliveryOutcome>;
|
|
62
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Presentation Port — 판단 요청을 사람에게 건네는 경계 (C-08 §1).
|
|
2
|
+
//
|
|
3
|
+
// `ApprovalChannel`(ports/approval.ts)과의 관계를 먼저 밝힌다:
|
|
4
|
+
// ApprovalChannel 요청 **하나**를 보이고 갱신한다. 이미 있고, 그대로 쓴다
|
|
5
|
+
// PresentationPort 요청 **묶음**을 건넨다. 이것이 여기서 새로 생기는 것이다
|
|
6
|
+
//
|
|
7
|
+
// 둘을 합치지 않는 이유: 묶음 전달을 제공하지만 그 자리에서 결정을 못 받는 채널이 있고
|
|
8
|
+
// (메일이 그렇다), 반대도 있다. 하나의 인터페이스로 묶으면 그런 채널이 전체를 구현하지
|
|
9
|
+
// 못하거나 빈 구현으로 거짓말을 하게 된다.
|
|
10
|
+
//
|
|
11
|
+
// 이 Port에는 결정 제출 표면이 없다 (C-08 §4). 묶고 보여주는 데까지이며, approve/dismiss는
|
|
12
|
+
// 사람의 명시적 의사표현을 받아 기존 결정 경로로만 간다.
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DecisionSummary, DecisionView } from '../core/view/decision-view.ts';
|
|
2
|
+
/** 표현 밀도. 같은 view에서 채널 UX에 맞게 접거나 펼친다 (C-01 §3). */
|
|
3
|
+
export type RenderDensity = 'summary' | 'full' | 'collapsible';
|
|
4
|
+
/**
|
|
5
|
+
* 렌더 결과. Core는 내용물을 해석하지 않는다 — 문자열이든 채널별 블록 구조든
|
|
6
|
+
* 그대로 Channel에 넘긴다.
|
|
7
|
+
*/
|
|
8
|
+
export type Rendered = {
|
|
9
|
+
density: RenderDensity;
|
|
10
|
+
text: string;
|
|
11
|
+
blocks?: unknown;
|
|
12
|
+
};
|
|
13
|
+
export interface Renderer {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
/**
|
|
16
|
+
* 상세 표현. reference는 어떤 density에서도 생략하지 않는다 — 사용자가 다른 채널에서
|
|
17
|
+
* 같은 요청을 지목하는 유일한 수단이기 때문이다 (C-01 §2).
|
|
18
|
+
* Stored Packet과 Current Context Overlay는 구분해 보여야 한다 (C-01 §6).
|
|
19
|
+
*/
|
|
20
|
+
renderDecision(view: DecisionView, density: RenderDensity): Rendered;
|
|
21
|
+
/** 목록 표현. 복수 후보를 사람이 고를 수 있게 나열한다 (C-01 §11). */
|
|
22
|
+
renderList(items: readonly DecisionSummary[]): Rendered;
|
|
23
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Renderer Port — Shared Decision View Model을 사람이 읽는 표현으로 바꾸는 경계.
|
|
2
|
+
// CLI·Messenger·Web UI가 같은 view를 각자의 방식으로 그리되, 필드의 의미와 request
|
|
3
|
+
// reference는 바꾸지 않는다 (C-01 §3).
|
|
4
|
+
//
|
|
5
|
+
// Approval Channel과 역할이 다르다: Channel은 전달과 결정 수신, Renderer는 표현 생성.
|
|
6
|
+
// 한 Adapter가 둘 다 구현할 수 있지만 계약은 분리해 둔다.
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** 논의 한 조각. 전문을 통째로 넘기지 않기 위해 조사 단계가 개수를 정한다. */
|
|
2
|
+
export type ContextComment = {
|
|
3
|
+
id: string;
|
|
4
|
+
author: string;
|
|
5
|
+
at: string;
|
|
6
|
+
body: string;
|
|
7
|
+
/** 아직 닫히지 않은 논의인가. provider가 알려주지 않으면 생략한다 — 추측하지 않는다. */
|
|
8
|
+
unresolved?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type ResourceSnapshot = {
|
|
11
|
+
reference: string;
|
|
12
|
+
state: string;
|
|
13
|
+
title: string;
|
|
14
|
+
body?: string;
|
|
15
|
+
author?: string;
|
|
16
|
+
assignees?: readonly string[];
|
|
17
|
+
labels?: readonly string[];
|
|
18
|
+
updatedAt: string;
|
|
19
|
+
revisionMarker: string;
|
|
20
|
+
/** 연결된 다른 리소스. 문법은 adapter 소관이고 Core는 식별자로만 다룬다. */
|
|
21
|
+
related?: readonly string[];
|
|
22
|
+
/** 사라졌거나 접근할 수 없다. 없는 것과 못 읽는 것을 구분해야 판정이 성립한다. */
|
|
23
|
+
missing?: boolean;
|
|
24
|
+
};
|
|
25
|
+
export type CommentQuery = {
|
|
26
|
+
/** 최근 몇 개까지. 조사 depth가 정한다 — Port가 기본값을 강요하지 않는다. */
|
|
27
|
+
limit?: number;
|
|
28
|
+
/** 이 시각 이후만. 직전 관측 이후의 변화를 볼 때 쓴다. */
|
|
29
|
+
since?: string;
|
|
30
|
+
};
|
|
31
|
+
export interface ResourceContextPort {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
getResource(reference: string): Promise<ResourceSnapshot>;
|
|
34
|
+
getComments(reference: string, query?: CommentQuery): Promise<ContextComment[]>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 경위 조회 (capability `context.history`). 별도 인터페이스인 이유는 제공하지 못하는
|
|
38
|
+
* adapter가 흔하기 때문이다 — 하나로 묶으면 이력을 모르는 adapter가 전체를 구현하지
|
|
39
|
+
* 못하거나 빈 배열로 거짓말을 하게 된다.
|
|
40
|
+
*/
|
|
41
|
+
export type HistoryEvent = {
|
|
42
|
+
at: string;
|
|
43
|
+
actor: string;
|
|
44
|
+
/** provider의 사건 어휘를 그대로. Core는 표시하고 나열할 뿐 해석하지 않는다. */
|
|
45
|
+
kind: string;
|
|
46
|
+
detail?: string;
|
|
47
|
+
};
|
|
48
|
+
export interface HistoryPort {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
getHistory(reference: string, limit?: number): Promise<HistoryEvent[]>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Resource Context Port — 리소스 하나의 현재 내용을 읽는다 (C-09 §2).
|
|
2
|
+
//
|
|
3
|
+
// 기존 ScmPort.getThread는 **변경 마커만** 돌려준다(Drift Guard가 대조할 값). 조사는
|
|
4
|
+
// 실제 내용을 읽어야 하므로 같은 Port에 얹을 수 없다 — 마커를 얻으려던 호출이 매번
|
|
5
|
+
// 스레드 전문을 끌고 오게 된다.
|
|
6
|
+
//
|
|
7
|
+
// 여기 있는 것은 읽기뿐이다. 무엇이 중요한 댓글인지, 무엇을 해야 하는지는 판정이고
|
|
8
|
+
// Core가 한다.
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CanonicalSnapshot } from '../core/model/entities.ts';
|
|
2
|
+
/** 스레드(Issue/PR/Review 등) 하나의 현재 상태. Drift Guard가 대조하는 값이다. */
|
|
3
|
+
export type ThreadSnapshot = {
|
|
4
|
+
reference: string;
|
|
5
|
+
lastEventId: string;
|
|
6
|
+
/** 스레드가 사라졌거나 접근 불가 — 실행 중단 사유가 된다. */
|
|
7
|
+
missing?: boolean;
|
|
8
|
+
};
|
|
9
|
+
/** canonical source의 현재 baseline. multi-source이므로 source별로 조회한다 (OM §8). */
|
|
10
|
+
export type BaselineQuery = {
|
|
11
|
+
sourceId: string;
|
|
12
|
+
ref?: string;
|
|
13
|
+
paths?: readonly string[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Grant가 지시하는 단일 외부 Action. Executor는 payload를 재작성하지 않는다 —
|
|
17
|
+
* 사람이 승인한 내용 그대로 나간다.
|
|
18
|
+
*/
|
|
19
|
+
export type ExternalAction = {
|
|
20
|
+
action: string;
|
|
21
|
+
target: string;
|
|
22
|
+
payload: string;
|
|
23
|
+
};
|
|
24
|
+
export type ExternalActionResult = {
|
|
25
|
+
ok: true;
|
|
26
|
+
resultRef: string;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
error: string;
|
|
30
|
+
};
|
|
31
|
+
export interface ScmPort {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
getThread(reference: string): Promise<ThreadSnapshot>;
|
|
34
|
+
getBaselines(queries: readonly BaselineQuery[]): Promise<CanonicalSnapshot[]>;
|
|
35
|
+
/**
|
|
36
|
+
* 외부 쓰기. Grant를 검증하고 Drift Guard를 통과시킨 Executor만 호출한다 —
|
|
37
|
+
* Port 자체는 권한을 판단하지 않으므로, 호출 지점이 좁게 유지되는 것이 계약이다.
|
|
38
|
+
*/
|
|
39
|
+
execute(action: ExternalAction): Promise<ExternalActionResult>;
|
|
40
|
+
}
|