@lazyingart/agintiflow 0.20.248 → 0.20.250

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.
@@ -330,3 +330,52 @@ event sequence, a project handoff with exact status and verification commands,
330
330
  and no lingering job tmux session. No source patch or npm release was needed;
331
331
  this run validates the published package's general long-job, resume, context,
332
332
  deduplication, and cleanup contracts without task-specific routing.
333
+
334
+ ### Host-managed response roles and permission integrity
335
+
336
+ `permission-resilient-synthesis-030` traced a real LabCanvas career-report
337
+ failure to the integration boundary rather than weakening AgInTiFlow's
338
+ permission guard. LabCanvas invoked the host-managed report under the role
339
+ `career_research`, while its response-only classifier recognized only the older
340
+ `career_daily` alias. The model was consequently offered file tools in a safe,
341
+ read-only run; when it tried to save its already synthesized report,
342
+ AgInTiFlow correctly persisted a `permission_required` pause.
343
+
344
+ LabCanvas now classifies the full `career-research-*` and
345
+ `daily-organizer-*` role families as host-managed response turns. Those turns
346
+ use AgInTi's `chatops` profile with shell, file, and auxiliary tools disabled;
347
+ the host owns persistence, compilation, quality validation, and delivery.
348
+ General worker roles retain their existing writable Docker contract, and safe
349
+ mode still blocks genuine unapproved writes.
350
+
351
+ A fresh installed `0.20.248` DeepSeek run completed the imperfect synthesis
352
+ prompt in one model turn. Independent event inspection found zero tool calls,
353
+ zero permission events, one normal `session.finished`, a complete 1,893-byte
354
+ answer, and no task artifact mutation. This was an AgenticApp integration fix;
355
+ no AgInTiFlow runtime or npm release change was required.
356
+
357
+ ### Reader-facing report quality and local editorial routing
358
+
359
+ `research-pdf-quality-034` exercised a normal scheduled-research packet through
360
+ the AgInTi-backed LabCanvas worker. DeepSeek revised an existing local report
361
+ into a 17,557-byte Chinese scientific review with nine traceable sources,
362
+ source-level methods/results/limitations, cross-source synthesis and tensions,
363
+ explicit evidence boundaries, actionable experiments, and references. The
364
+ first host compile exposed a nearly empty final page, and the prior agent repair
365
+ claimed success without changing the source.
366
+
367
+ LabCanvas now treats the PDF as a different deliverable from the concise chat
368
+ brief. It audits every configured content dimension, keeps orchestration
369
+ provenance out of the reader document, extracts text per page, renders private
370
+ page previews, rejects orphan pages, retries one conservative compact layout,
371
+ rebuilds stale sibling PDFs, and can deterministically adopt a corrected
372
+ host-built PDF during stored-result repair. The accepted four-page PDF has
373
+ embedded CJK fonts, page body counts `1584, 1988, 1416, 2058`, and no visual or
374
+ layout issues. Stored replay covered every task item with no model rerun or
375
+ external write.
376
+
377
+ The scenario also exposed two general AgInTi routing false positives. Explicit
378
+ local report paths and existing-document revision language now count as local
379
+ workspace intent, so surrounding research policy does not force a new deep
380
+ research route. The phrase `page-safe` no longer creates a browser-evidence
381
+ requirement. Focused routing/evidence smokes and the full npm suite pass.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.248",
3
+ "version": "0.20.250",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -99,6 +99,7 @@
99
99
  "scripts/smoke-localllm-auto-max.js",
100
100
  "scripts/smoke-localllm-code-routing.js",
101
101
  "scripts/smoke-local-failure-recovery.js",
102
+ "scripts/smoke-planning-timeout-recovery.js",
102
103
  "scripts/smoke-local-resource-policy.js",
103
104
  "scripts/smoke-localllm-model-tiers.js",
104
105
  "scripts/smoke-localllm-provider.js",
@@ -106,6 +107,7 @@
106
107
  "scripts/smoke-truthful-completion.js",
107
108
  "scripts/smoke-document-artifact-quality.js",
108
109
  "scripts/smoke-writing-specialist-routing.js",
110
+ "scripts/smoke-workspace-secret-false-positive.js",
109
111
  "scripts/eval-provider-attribution.js",
110
112
  "scripts/local-first-agent-eval.mjs",
111
113
  "scripts/fixtures/local-first-agent-eval-fixtures.mjs",
@@ -171,6 +173,7 @@
171
173
  "smoke:localllm-auto-max": "node scripts/smoke-localllm-auto-max.js",
172
174
  "smoke:localllm-code-routing": "node scripts/smoke-localllm-code-routing.js",
173
175
  "smoke:local-failure-recovery": "node scripts/smoke-local-failure-recovery.js",
176
+ "smoke:planning-timeout-recovery": "node scripts/smoke-planning-timeout-recovery.js",
174
177
  "smoke:local-resource-policy": "node scripts/smoke-local-resource-policy.js",
175
178
  "smoke:context-budget-recovery": "node scripts/smoke-context-budget-recovery.js",
176
179
  "smoke:localllm-model-tiers": "node scripts/smoke-localllm-model-tiers.js",
@@ -179,6 +182,7 @@
179
182
  "smoke:truthful-completion": "node scripts/smoke-truthful-completion.js",
180
183
  "smoke:document-artifact-quality": "node scripts/smoke-document-artifact-quality.js",
181
184
  "smoke:writing-specialist-routing": "node scripts/smoke-writing-specialist-routing.js",
185
+ "smoke:workspace-secret-false-positive": "node scripts/smoke-workspace-secret-false-positive.js",
182
186
  "smoke:mcp": "node scripts/smoke-mcp.js",
183
187
  "smoke:model-roles": "node scripts/smoke-model-roles.js",
184
188
  "smoke:platform": "node scripts/smoke-platform.js",
@@ -223,7 +227,8 @@
223
227
  "storage:migrate": "node bin/aginti-cli.js storage migrate",
224
228
  "publish:env": "node scripts/npm-publish-from-env.js publish --access public",
225
229
  "publish:env:whoami": "node scripts/npm-publish-from-env.js whoami",
226
- "test": "npm run check && npm run smoke:integration-production-mount && npm run smoke:integration-production-runtime-bundle && npm run smoke:integration-retained-idempotency-store && npm run smoke:integration-storage-authority && npm run smoke:integration-retained-durable-common && npm run smoke:integration-retained-file-lock && npm run smoke:integration-retained-event-ledger && npm run smoke:integration-retained-event-ledger-bundle && npm run smoke:integration-retained-repository-kernel && npm run smoke:integration-retained-session-state-store && npm run smoke:integration-retained-native-session-repository-state && npm run smoke:integration-retained-native-execution-evidence && npm run smoke:integration-retained-runtime-repository-surface && npm run smoke:integration-retained-runtime-repository-phase-b && npm run smoke:integration-runtime-repository-contract && npm run smoke:integration-runtime-authority && npm run smoke:localllm-provider && npm run smoke:localllm-model-tiers && npm run smoke:localllm-code-routing && npm run smoke:local-failure-recovery && npm run smoke:localllm-auto-max && npm run smoke:local-resource-policy && npm run smoke:context-budget-recovery && npm run smoke:session-runtime && npm run smoke:runtime-core && npm run smoke:progressive-tools && npm run smoke:truthful-completion && npm run smoke:document-artifact-quality && npm run smoke:writing-specialist-routing && npm run eval:local-first-agent && npm run eval:provider-attribution && npm run smoke:runtime-compat && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:math-rendering && npm run smoke:web-ui && npm run smoke:web-autostart && npm run smoke:webapp-command && npm run smoke:web-port-fallback && npm run smoke:docker-command && npm run smoke:coding-tools && npm run smoke:dynamic-step-budget && npm run smoke:execution-policy && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && npm run smoke:perception-research && npm run smoke:deep-research && npm run smoke:public-research && npm run smoke:safe-chat && npm run smoke:auth && npm run smoke:agentlink && npm run smoke:canvas-artifacts && npm run smoke:capabilities && npm run smoke:mcp && npm run smoke:model-roles && npm run smoke:platform && npm run smoke:permission-modes && npm run smoke:skills && npm run smoke:skillmesh && npm run smoke:supervision-ledger && npm run smoke:tmux-tools && npm run smoke:long-jobs && npm run smoke:run-stdin && npm run smoke:cli-chat && npm run smoke:inbox",
230
+ "pretest": "npm run smoke:planning-timeout-recovery",
231
+ "test": "npm run check && npm run smoke:integration-production-mount && npm run smoke:integration-production-runtime-bundle && npm run smoke:integration-retained-idempotency-store && npm run smoke:integration-storage-authority && npm run smoke:integration-retained-durable-common && npm run smoke:integration-retained-file-lock && npm run smoke:integration-retained-event-ledger && npm run smoke:integration-retained-event-ledger-bundle && npm run smoke:integration-retained-repository-kernel && npm run smoke:integration-retained-session-state-store && npm run smoke:integration-retained-native-session-repository-state && npm run smoke:integration-retained-native-execution-evidence && npm run smoke:integration-retained-runtime-repository-surface && npm run smoke:integration-retained-runtime-repository-phase-b && npm run smoke:integration-runtime-repository-contract && npm run smoke:integration-runtime-authority && npm run smoke:localllm-provider && npm run smoke:localllm-model-tiers && npm run smoke:localllm-code-routing && npm run smoke:local-failure-recovery && npm run smoke:localllm-auto-max && npm run smoke:local-resource-policy && npm run smoke:context-budget-recovery && npm run smoke:session-runtime && npm run smoke:runtime-core && npm run smoke:progressive-tools && npm run smoke:truthful-completion && npm run smoke:document-artifact-quality && npm run smoke:writing-specialist-routing && npm run smoke:workspace-secret-false-positive && npm run eval:local-first-agent && npm run eval:provider-attribution && npm run smoke:runtime-compat && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:math-rendering && npm run smoke:web-ui && npm run smoke:web-autostart && npm run smoke:webapp-command && npm run smoke:web-port-fallback && npm run smoke:docker-command && npm run smoke:coding-tools && npm run smoke:dynamic-step-budget && npm run smoke:execution-policy && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && npm run smoke:perception-research && npm run smoke:deep-research && npm run smoke:public-research && npm run smoke:safe-chat && npm run smoke:auth && npm run smoke:agentlink && npm run smoke:canvas-artifacts && npm run smoke:capabilities && npm run smoke:mcp && npm run smoke:model-roles && npm run smoke:platform && npm run smoke:permission-modes && npm run smoke:skills && npm run smoke:skillmesh && npm run smoke:supervision-ledger && npm run smoke:tmux-tools && npm run smoke:long-jobs && npm run smoke:run-stdin && npm run smoke:cli-chat && npm run smoke:inbox",
227
232
  "pack:dry-run": "npm pack --dry-run",
228
233
  "smoke:capabilities": "node scripts/smoke-capabilities.js"
229
234
  },
@@ -971,6 +971,55 @@ try {
971
971
  const pythonDemoPolicy = evaluateCommandPolicy("python3 demo.py 2>&1", dockerWorkspaceNoInstallsPolicy);
972
972
  assert(pythonDemoPolicy.allowed, "workspace-local python demo script should be allowed without package installs");
973
973
  assert(pythonDemoPolicy.category === "toolchain", "workspace-local python demo script should be classified as toolchain");
974
+ const broadLocalProbePolicy = evaluateCommandPolicy(
975
+ 'python3 --version; echo "---IMPORT TEST---"; python3 -c "import pathlib; print(pathlib.Path.cwd())" 2>&1; echo "---FILES---"; ls -la 2>&1 | head -20',
976
+ dockerWorkspaceNoInstallsPolicy
977
+ );
978
+ assert(
979
+ broadLocalProbePolicy.allowed && broadLocalProbePolicy.category === "general-shell",
980
+ "networkless general shell inside docker-workspace should not require package-install permission"
981
+ );
982
+ const broadLocalHostProbePolicy = evaluateCommandPolicy(
983
+ 'python3 -c "import pathlib; print(pathlib.Path.cwd())"',
984
+ { ...hostWorkspacePolicy, packageInstallPolicy: "block" }
985
+ );
986
+ assert(
987
+ !broadLocalHostProbePolicy.allowed,
988
+ "general shell on the host should remain blocked without trusted destructive access"
989
+ );
990
+ const explicitBlockedInstallPolicy = evaluateCommandPolicy(
991
+ "python3 -m pip install requests",
992
+ dockerWorkspaceNoInstallsPolicy
993
+ );
994
+ assert(
995
+ !explicitBlockedInstallPolicy.allowed && explicitBlockedInstallPolicy.category === "package-install",
996
+ "decoupling broad shell from install authorization must not allow explicit package installs"
997
+ );
998
+ const destructiveNoInstallsPolicy = evaluateCommandPolicy("rm -rf reports", dockerWorkspaceNoInstallsPolicy);
999
+ assert(
1000
+ !destructiveNoInstallsPolicy.allowed && destructiveNoInstallsPolicy.category === "destructive",
1001
+ "decoupling broad shell from install authorization must not weaken destructive-command guards"
1002
+ );
1003
+ const multiParagraphCommitPolicy = evaluateCommandPolicy(
1004
+ 'git commit -m "Harden LabShare" -m "Fix path traversal, avoid shell injection, and preserve normal use."',
1005
+ dockerWorkspaceNoInstallsPolicy
1006
+ );
1007
+ assert(
1008
+ multiParagraphCommitPolicy.allowed &&
1009
+ multiParagraphCommitPolicy.category === "git-workflow" &&
1010
+ multiParagraphCommitPolicy.gitOnly === true,
1011
+ "a bounded multi-paragraph Git commit message should remain a Git-only workflow"
1012
+ );
1013
+ const boundedTaskCommitPolicy = evaluateCommandPolicy(
1014
+ "git add -- 'labshare.py' 'tests/test_labshare.py' 'SECURITY.md' && git commit -m 'Harden LabShare'",
1015
+ dockerWorkspaceNoInstallsPolicy
1016
+ );
1017
+ assert(
1018
+ boundedTaskCommitPolicy.allowed &&
1019
+ boundedTaskCommitPolicy.category === "git-workflow" &&
1020
+ boundedTaskCommitPolicy.gitOnly === true,
1021
+ "the task-owned add-and-commit routine should remain a bounded Git workflow"
1022
+ );
974
1023
  const rVersionProbePolicy = evaluateCommandPolicy("R --version 2>&1", dockerWorkspaceNoInstallsPolicy);
975
1024
  assert(rVersionProbePolicy.allowed, "R version probe should be allowed without package installs");
976
1025
  assert(rVersionProbePolicy.category === "read-only", "R version probe should be classified as read-only");
@@ -1218,6 +1267,38 @@ try {
1218
1267
  });
1219
1268
  assert(permissionAdvice.suggestedCommand.includes("coding-policy-smoke"), "permission advice did not include resume session id");
1220
1269
  assert(permissionAdvice.suggestedCommand.includes("--sandbox-mode docker-workspace"), "permission advice did not suggest docker-workspace recovery");
1270
+ const wrongWorkspaceCdAdvice = buildPermissionAdvice({
1271
+ toolName: "run_command",
1272
+ args: {
1273
+ command:
1274
+ "cd /home/example/project-parent && python3 -m unittest discover -s tests -v",
1275
+ },
1276
+ guard: {
1277
+ category: "blocked",
1278
+ reason:
1279
+ "cd target must be a safe workspace-relative directory: /home/example/project-parent",
1280
+ },
1281
+ config: {
1282
+ ...dockerWorkspacePolicy,
1283
+ commandCwd: "/home/example/project-parent/current-project",
1284
+ },
1285
+ state: { sessionId: "coding-wrong-workspace-cd-smoke" },
1286
+ });
1287
+ assert(
1288
+ wrongWorkspaceCdAdvice.autoRecover === true,
1289
+ "an unnecessary outside-workspace cd should be corrected in-turn instead of pausing"
1290
+ );
1291
+ assert(
1292
+ /configured project root/i.test(wrongWorkspaceCdAdvice.instruction),
1293
+ "workspace cd correction did not direct the model back to the configured project root"
1294
+ );
1295
+ assert(
1296
+ !shouldPauseForPermissionAdvice({
1297
+ blocked: true,
1298
+ permissionAdvice: wrongWorkspaceCdAdvice,
1299
+ }),
1300
+ "recoverable workspace cd correction still paused the session"
1301
+ );
1221
1302
  const destructiveAdvice = buildPermissionAdvice({
1222
1303
  toolName: "run_command",
1223
1304
  args: { command: "rm -rf reports && git reset --hard" },
@@ -1367,6 +1448,32 @@ try {
1367
1448
  !shouldPauseForPermissionAdvice({ blocked: true, permissionAdvice: dynamicEvidenceAdvice }),
1368
1449
  "dynamic evidence filename recovery still produced a permission pause"
1369
1450
  );
1451
+ const malformedPatchGuard = {
1452
+ allowed: false,
1453
+ category: "workspace-patch",
1454
+ reason: "Patch did not contain any supported file operations.",
1455
+ permissionAdvice: {
1456
+ category: "workspace-patch",
1457
+ autoRecover: true,
1458
+ summary: "The patch format or exact context was not accepted; this is not a permission blocker.",
1459
+ instruction: "Inspect the exact file and retry with a supported focused edit.",
1460
+ },
1461
+ };
1462
+ const malformedPatchAdvice = buildPermissionAdvice({
1463
+ toolName: "apply_patch",
1464
+ args: { patch: "*** a/labshare.py\n*** b/labshare.py" },
1465
+ guard: malformedPatchGuard,
1466
+ config: dockerWorkspacePolicy,
1467
+ state: { sessionId: "coding-malformed-patch-smoke" },
1468
+ });
1469
+ assert(
1470
+ malformedPatchAdvice === malformedPatchGuard.permissionAdvice,
1471
+ "explicit recoverable workspace-patch advice was replaced by generic permission advice"
1472
+ );
1473
+ assert(
1474
+ !shouldPauseForPermissionAdvice({ blocked: true, permissionAdvice: malformedPatchAdvice }),
1475
+ "recoverable malformed patch advice still paused the session for permission"
1476
+ );
1370
1477
  const destructiveDynamicEvidenceAdvice = buildPermissionAdvice({
1371
1478
  toolName: "run_command",
1372
1479
  args: {
@@ -1408,6 +1515,104 @@ try {
1408
1515
  /once for each rendered page/i.test(documentPageBatchAdvice.instruction),
1409
1516
  "Word document page batching did not recover into separate visual checks"
1410
1517
  );
1518
+ const alreadyCommittedAdvice = buildFailedCommandAdvice({
1519
+ args: { command: "git commit -m 'Focused repair'" },
1520
+ commandPolicy: evaluateCommandPolicy("git commit -m 'Focused repair'", dockerWorkspacePolicy),
1521
+ commandResult: {
1522
+ ok: false,
1523
+ exitCode: 1,
1524
+ stdout: "On branch main\nnothing to commit, working tree clean\n",
1525
+ },
1526
+ config: dockerWorkspacePolicy,
1527
+ state: {
1528
+ meta: {
1529
+ goalContract: { revision: 9 },
1530
+ projectVerification: { mutationRevision: 4 },
1531
+ durableGitEvidence: [{ action: "commit", goalRevision: 9, mutationRevision: 4 }],
1532
+ },
1533
+ },
1534
+ });
1535
+ assert(
1536
+ alreadyCommittedAdvice?.failureKind === "repository-already-committed" &&
1537
+ alreadyCommittedAdvice.autoRecover === true &&
1538
+ /should not be retried/i.test(alreadyCommittedAdvice.summary),
1539
+ "a clean no-op commit with current durable evidence did not converge"
1540
+ );
1541
+ const sameTaskContinuationAdvice = buildFailedCommandAdvice({
1542
+ args: { command: "git commit -m 'Focused repair'" },
1543
+ commandPolicy: evaluateCommandPolicy("git commit -m 'Focused repair'", dockerWorkspacePolicy),
1544
+ commandResult: {
1545
+ ok: false,
1546
+ exitCode: 1,
1547
+ stdout: "On branch main\nnothing to commit, working tree clean\n",
1548
+ },
1549
+ config: dockerWorkspacePolicy,
1550
+ state: {
1551
+ meta: {
1552
+ goalContract: {
1553
+ revision: 10,
1554
+ history: [
1555
+ { revision: 9, taskHash: "task-a" },
1556
+ { revision: 10, taskHash: "task-a" },
1557
+ ],
1558
+ },
1559
+ projectVerification: { mutationRevision: 4 },
1560
+ durableGitEvidence: [{ action: "commit", goalRevision: 9, mutationRevision: 4 }],
1561
+ },
1562
+ },
1563
+ });
1564
+ assert(
1565
+ sameTaskContinuationAdvice?.failureKind === "repository-already-committed",
1566
+ "a same-task continuation invalidated a clean commit at the current mutation revision"
1567
+ );
1568
+ const newTaskAdvice = buildFailedCommandAdvice({
1569
+ args: { command: "git commit -m 'Focused repair'" },
1570
+ commandPolicy: evaluateCommandPolicy("git commit -m 'Focused repair'", dockerWorkspacePolicy),
1571
+ commandResult: {
1572
+ ok: false,
1573
+ exitCode: 1,
1574
+ stdout: "On branch main\nnothing to commit, working tree clean\n",
1575
+ },
1576
+ config: dockerWorkspacePolicy,
1577
+ state: {
1578
+ meta: {
1579
+ goalContract: {
1580
+ revision: 10,
1581
+ history: [
1582
+ { revision: 9, taskHash: "task-a" },
1583
+ { revision: 10, taskHash: "task-b" },
1584
+ ],
1585
+ },
1586
+ projectVerification: { mutationRevision: 4 },
1587
+ durableGitEvidence: [{ action: "commit", goalRevision: 9, mutationRevision: 4 }],
1588
+ },
1589
+ },
1590
+ });
1591
+ assert(
1592
+ newTaskAdvice === null,
1593
+ "a commit from a prior task satisfied a genuinely new task"
1594
+ );
1595
+ const staleCommitAdvice = buildFailedCommandAdvice({
1596
+ args: { command: "git commit -m 'Focused repair'" },
1597
+ commandPolicy: evaluateCommandPolicy("git commit -m 'Focused repair'", dockerWorkspacePolicy),
1598
+ commandResult: {
1599
+ ok: false,
1600
+ exitCode: 1,
1601
+ stdout: "On branch main\nnothing to commit, working tree clean\n",
1602
+ },
1603
+ config: dockerWorkspacePolicy,
1604
+ state: {
1605
+ meta: {
1606
+ goalContract: { revision: 9 },
1607
+ projectVerification: { mutationRevision: 4 },
1608
+ durableGitEvidence: [{ action: "commit", goalRevision: 8, mutationRevision: 4 }],
1609
+ },
1610
+ },
1611
+ });
1612
+ assert(
1613
+ staleCommitAdvice === null,
1614
+ "a stale commit from an earlier goal revision satisfied a fresh commit request"
1615
+ );
1411
1616
  const failedNetworkAdvice = buildFailedCommandAdvice({
1412
1617
  args: { command: "git clone https://github.com/lazyingart/AgInTiFlow.git" },
1413
1618
  commandPolicy: clonePolicy,
@@ -6,12 +6,52 @@ import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { promisify } from "node:util";
9
+ import {
10
+ dockerWorkspaceAliasMounts,
11
+ ensureDockerGitIdentity,
12
+ } from "../src/docker-sandbox.js";
9
13
 
10
14
  const execFileAsync = promisify(execFile);
11
15
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
12
16
  const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-docker-command-"));
13
17
 
14
18
  try {
19
+ const identityRepo = path.join(tempRoot, "identity-repo");
20
+ const dockerState = path.join(tempRoot, "docker-state");
21
+ await fs.mkdir(identityRepo, { recursive: true });
22
+ await fs.mkdir(path.join(dockerState, "home"), { recursive: true });
23
+ await execFileAsync("git", ["init", identityRepo]);
24
+ await execFileAsync("git", ["-C", identityRepo, "config", "user.name", "AgInTi Test Author"]);
25
+ await execFileAsync("git", ["-C", identityRepo, "config", "user.email", "aginti-test@example.invalid"]);
26
+ assert.deepEqual(
27
+ dockerWorkspaceAliasMounts({ commandCwd: identityRepo, sandboxMode: "docker-workspace" }),
28
+ [identityRepo],
29
+ "Docker workspace mode should preserve the exact host workspace path as a scoped writable alias"
30
+ );
31
+ assert.deepEqual(
32
+ dockerWorkspaceAliasMounts({ commandCwd: identityRepo, sandboxMode: "docker-readonly" }),
33
+ [],
34
+ "Read-only Docker mode must not create a writable host-path alias"
35
+ );
36
+ const identity = await ensureDockerGitIdentity(
37
+ { commandCwd: identityRepo, dockerStateDir: dockerState },
38
+ {
39
+ root: dockerState,
40
+ home: path.join(dockerState, "home"),
41
+ cache: path.join(dockerState, "cache"),
42
+ env: path.join(dockerState, "env"),
43
+ }
44
+ );
45
+ assert.equal(identity.configured, true, "Docker Git identity should reuse an existing repository author");
46
+ const identityConfig = path.join(dockerState, "home", ".gitconfig.aginti-author");
47
+ const [storedName, storedEmail] = await Promise.all([
48
+ execFileAsync("git", ["config", "--file", identityConfig, "--get", "user.name"]),
49
+ execFileAsync("git", ["config", "--file", identityConfig, "--get", "user.email"]),
50
+ ]);
51
+ assert.equal(storedName.stdout.trim(), "AgInTi Test Author");
52
+ assert.equal(storedEmail.stdout.trim(), "aginti-test@example.invalid");
53
+ assert.equal((await fs.stat(identityConfig)).mode & 0o777, 0o600, "Docker author config should be private");
54
+
15
55
  const { stdout } = await execFileAsync(process.execPath, [path.join(repoRoot, "bin/aginti-cli.js"), "docker", "status", "--json", "--cwd", tempRoot], {
16
56
  cwd: tempRoot,
17
57
  env: {