@qa-ai-stlc/core 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +13 -0
  2. package/dist/auth-session-store.d.ts +15 -0
  3. package/dist/auth-session-store.d.ts.map +1 -0
  4. package/dist/auth-session-store.js +34 -0
  5. package/dist/auth-session-store.js.map +1 -0
  6. package/dist/auth.d.ts +28 -0
  7. package/dist/auth.d.ts.map +1 -0
  8. package/dist/auth.js +77 -0
  9. package/dist/auth.js.map +1 -0
  10. package/dist/browser-doctor.d.ts +29 -0
  11. package/dist/browser-doctor.d.ts.map +1 -0
  12. package/dist/browser-doctor.js +130 -0
  13. package/dist/browser-doctor.js.map +1 -0
  14. package/dist/config-loader.d.ts +9 -0
  15. package/dist/config-loader.d.ts.map +1 -0
  16. package/dist/config-loader.js +40 -0
  17. package/dist/config-loader.js.map +1 -0
  18. package/dist/errors.d.ts +15 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +18 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/evidence-store.d.ts +43 -0
  23. package/dist/evidence-store.d.ts.map +1 -0
  24. package/dist/evidence-store.js +81 -0
  25. package/dist/evidence-store.js.map +1 -0
  26. package/dist/hash.d.ts +6 -0
  27. package/dist/hash.d.ts.map +1 -0
  28. package/dist/hash.js +19 -0
  29. package/dist/hash.js.map +1 -0
  30. package/dist/index.d.ts +20 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +22 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/json-file.d.ts +6 -0
  35. package/dist/json-file.d.ts.map +1 -0
  36. package/dist/json-file.js +29 -0
  37. package/dist/json-file.js.map +1 -0
  38. package/dist/manifest-store.d.ts +27 -0
  39. package/dist/manifest-store.d.ts.map +1 -0
  40. package/dist/manifest-store.js +65 -0
  41. package/dist/manifest-store.js.map +1 -0
  42. package/dist/paths.d.ts +15 -0
  43. package/dist/paths.d.ts.map +1 -0
  44. package/dist/paths.js +33 -0
  45. package/dist/paths.js.map +1 -0
  46. package/dist/ports/browser-launcher.d.ts +31 -0
  47. package/dist/ports/browser-launcher.d.ts.map +1 -0
  48. package/dist/ports/browser-launcher.js +8 -0
  49. package/dist/ports/browser-launcher.js.map +1 -0
  50. package/dist/ports/clock.d.ts +6 -0
  51. package/dist/ports/clock.d.ts.map +1 -0
  52. package/dist/ports/clock.js +6 -0
  53. package/dist/ports/clock.js.map +1 -0
  54. package/dist/ports/file-system.d.ts +13 -0
  55. package/dist/ports/file-system.d.ts.map +1 -0
  56. package/dist/ports/file-system.js +25 -0
  57. package/dist/ports/file-system.js.map +1 -0
  58. package/dist/ports/http-client.d.ts +13 -0
  59. package/dist/ports/http-client.d.ts.map +1 -0
  60. package/dist/ports/http-client.js +12 -0
  61. package/dist/ports/http-client.js.map +1 -0
  62. package/dist/ports/logger.d.ts +9 -0
  63. package/dist/ports/logger.d.ts.map +1 -0
  64. package/dist/ports/logger.js +9 -0
  65. package/dist/ports/logger.js.map +1 -0
  66. package/dist/ports/process-runner.d.ts +17 -0
  67. package/dist/ports/process-runner.d.ts.map +1 -0
  68. package/dist/ports/process-runner.js +24 -0
  69. package/dist/ports/process-runner.js.map +1 -0
  70. package/dist/qa-store.d.ts +33 -0
  71. package/dist/qa-store.d.ts.map +1 -0
  72. package/dist/qa-store.js +65 -0
  73. package/dist/qa-store.js.map +1 -0
  74. package/dist/redaction.d.ts +11 -0
  75. package/dist/redaction.d.ts.map +1 -0
  76. package/dist/redaction.js +53 -0
  77. package/dist/redaction.js.map +1 -0
  78. package/dist/secret-scan.d.ts +7 -0
  79. package/dist/secret-scan.d.ts.map +1 -0
  80. package/dist/secret-scan.js +21 -0
  81. package/dist/secret-scan.js.map +1 -0
  82. package/dist/test-support/fake-browser-launcher.d.ts +16 -0
  83. package/dist/test-support/fake-browser-launcher.d.ts.map +1 -0
  84. package/dist/test-support/fake-browser-launcher.js +64 -0
  85. package/dist/test-support/fake-browser-launcher.js.map +1 -0
  86. package/dist/test-support/fake-file-system.d.ts +12 -0
  87. package/dist/test-support/fake-file-system.d.ts.map +1 -0
  88. package/dist/test-support/fake-file-system.js +33 -0
  89. package/dist/test-support/fake-file-system.js.map +1 -0
  90. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @qa-ai-stlc/core
2
+
3
+ The deterministic engine underneath the `qa` CLI and the MCP server. This package owns the
4
+ `.qa/` store: reading and validating `config.yaml`, hashing every registered artifact into
5
+ `manifest.json`, and normalizing project-relative paths so a hash or a reference recorded on one
6
+ OS still resolves on another.
7
+
8
+ Filesystem access, wall-clock time and logging are injected through small ports (`FileSystem`,
9
+ `Clock`, `Logger`) rather than called directly, so the engine's logic is testable without real
10
+ I/O and so hosts (CLI, MCP server, tests) can supply their own implementations.
11
+
12
+ Part of [QA-AI-STLC](https://github.com/Klim-101/QA-AI-STLC). See the repository root for
13
+ license, contributing and security information.
@@ -0,0 +1,15 @@
1
+ import type { StorageState } from './ports/browser-launcher.js';
2
+ import type { QaStore } from './qa-store.js';
3
+ /**
4
+ * Persists `storageState` per identity under `.qa/auth/`, outside the manifest and evidence: it
5
+ * holds live session cookies and tokens, which must never be treated as a registered, hashed
6
+ * artifact or shared the way evidence is (AGENTS.md 5.8, development plan section 6.4).
7
+ */
8
+ export declare class AuthSessionStore {
9
+ private readonly store;
10
+ constructor(store: QaStore);
11
+ private pathFor;
12
+ save(identityName: string, storageState: StorageState): Promise<void>;
13
+ load(identityName: string): Promise<StorageState | undefined>;
14
+ }
15
+ //# sourceMappingURL=auth-session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-session-store.d.ts","sourceRoot":"","sources":["../src/auth-session-store.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAS7C;;;;GAIG;AACH,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,OAAO;IAE3C,OAAO,CAAC,OAAO;IAIT,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrE,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;CAOpE"}
@@ -0,0 +1,34 @@
1
+ // Copyright The QA-AI-STLC Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { z } from 'zod';
4
+ // Playwright's own storageState shape has no published Zod schema to import; this is a loose
5
+ // structural check, not a full re-validation of every field.
6
+ const StorageStateSchema = z.object({
7
+ cookies: z.array(z.record(z.string(), z.unknown())),
8
+ origins: z.array(z.record(z.string(), z.unknown())),
9
+ });
10
+ /**
11
+ * Persists `storageState` per identity under `.qa/auth/`, outside the manifest and evidence: it
12
+ * holds live session cookies and tokens, which must never be treated as a registered, hashed
13
+ * artifact or shared the way evidence is (AGENTS.md 5.8, development plan section 6.4).
14
+ */
15
+ export class AuthSessionStore {
16
+ store;
17
+ constructor(store) {
18
+ this.store = store;
19
+ }
20
+ pathFor(identityName) {
21
+ return `auth/${identityName}.json`;
22
+ }
23
+ async save(identityName, storageState) {
24
+ await this.store.writeJson(this.pathFor(identityName), storageState);
25
+ }
26
+ async load(identityName) {
27
+ const relativePath = this.pathFor(identityName);
28
+ if (!(await this.store.pathExists(relativePath))) {
29
+ return undefined;
30
+ }
31
+ return this.store.readJson(relativePath, StorageStateSchema);
32
+ }
33
+ }
34
+ //# sourceMappingURL=auth-session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-session-store.js","sourceRoot":"","sources":["../src/auth-session-store.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAEtC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,6FAA6F;AAC7F,6DAA6D;AAC7D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACnD,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;CACpD,CAAuC,CAAC;AAEzC;;;;GAIG;AACH,MAAM,OAAO,gBAAgB;IACE;IAA7B,YAA6B,KAAc;QAAd,UAAK,GAAL,KAAK,CAAS;IAAG,CAAC;IAEvC,OAAO,CAAC,YAAoB;QAClC,OAAO,QAAQ,YAAY,OAAO,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,YAAoB,EAAE,YAA0B;QACzD,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,CAAC;IACvE,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,YAAoB;QAC7B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAChD,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;YACjD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC;IAC/D,CAAC;CACF"}
package/dist/auth.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type { IdentityConfig } from '@qa-ai-stlc/schemas';
2
+ import type { BrowserLauncher, StorageState } from './ports/browser-launcher.js';
3
+ /**
4
+ * Attaches to a browser the operator has already signed into (`connectOverCDP`), covering SSO,
5
+ * MFA and custom redirects the framework never has to reproduce. Never closes the attached
6
+ * browser: it belongs to the operator, not the engine.
7
+ */
8
+ export declare function attachViaCdp(launcher: BrowserLauncher, endpointUrl: string): Promise<StorageState>;
9
+ export interface LoginCredentials {
10
+ readonly loginUrl: string;
11
+ readonly username: string;
12
+ readonly password: string;
13
+ readonly usernameSelector?: string;
14
+ readonly passwordSelector?: string;
15
+ readonly submitSelector?: string;
16
+ }
17
+ /** Scripted login with secrets from environment variables (development plan section 6.2 step 2). */
18
+ export declare function loginWithCredentials(launcher: BrowserLauncher, credentials: LoginCredentials): Promise<StorageState>;
19
+ export interface AuthenticateOptions {
20
+ /** Required, and only used, when `identity.auth` is `"cdp-attach"`. */
21
+ readonly cdpEndpointUrl?: string;
22
+ }
23
+ /**
24
+ * Produces a reusable session for one configured identity, dispatching to the CDP-attach or
25
+ * scripted-login path per `identity.auth` (development plan section 6.2).
26
+ */
27
+ export declare function authenticate(launcher: BrowserLauncher, identity: IdentityConfig, env: Readonly<Record<string, string | undefined>>, options?: AuthenticateOptions): Promise<StorageState>;
28
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAe,eAAe,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAS9F;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAUxG;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,oGAAoG;AACpG,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,eAAe,EACzB,WAAW,EAAE,gBAAgB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAcvB;AAED,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,cAAc,EACxB,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,EACjD,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,YAAY,CAAC,CAoCvB"}
package/dist/auth.js ADDED
@@ -0,0 +1,77 @@
1
+ // Copyright The QA-AI-STLC Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { QaError } from './errors.js';
4
+ // Generic fallbacks for a login form the operator has not described in config.yaml
5
+ // (development plan section 6.2); given only when `identity.selectors` omits one.
6
+ const DEFAULT_USERNAME_SELECTOR = 'input[type="email"], input[name="username"], input[id="username"], input[autocomplete="username"]';
7
+ const DEFAULT_PASSWORD_SELECTOR = 'input[type="password"]';
8
+ const DEFAULT_SUBMIT_SELECTOR = 'button[type="submit"], input[type="submit"]';
9
+ /**
10
+ * Attaches to a browser the operator has already signed into (`connectOverCDP`), covering SSO,
11
+ * MFA and custom redirects the framework never has to reproduce. Never closes the attached
12
+ * browser: it belongs to the operator, not the engine.
13
+ */
14
+ export async function attachViaCdp(launcher, endpointUrl) {
15
+ const browser = await launcher.connectOverCdp(endpointUrl);
16
+ const context = browser.contexts()[0];
17
+ if (context === undefined) {
18
+ throw new QaError('CDP_NO_CONTEXT', `No browser context found at "${endpointUrl}"`, {
19
+ remediation: 'Open at least one tab in the browser started with --remote-debugging-port before attaching.',
20
+ });
21
+ }
22
+ return context.storageState();
23
+ }
24
+ /** Scripted login with secrets from environment variables (development plan section 6.2 step 2). */
25
+ export async function loginWithCredentials(launcher, credentials) {
26
+ const browser = await launcher.launch();
27
+ try {
28
+ const context = await browser.newContext();
29
+ const page = await context.newPage();
30
+ await page.goto(credentials.loginUrl);
31
+ await page.fill(credentials.usernameSelector ?? DEFAULT_USERNAME_SELECTOR, credentials.username);
32
+ await page.fill(credentials.passwordSelector ?? DEFAULT_PASSWORD_SELECTOR, credentials.password);
33
+ await page.click(credentials.submitSelector ?? DEFAULT_SUBMIT_SELECTOR);
34
+ await page.waitForLoadState('networkidle');
35
+ return await context.storageState();
36
+ }
37
+ finally {
38
+ await browser.close();
39
+ }
40
+ }
41
+ /**
42
+ * Produces a reusable session for one configured identity, dispatching to the CDP-attach or
43
+ * scripted-login path per `identity.auth` (development plan section 6.2).
44
+ */
45
+ export async function authenticate(launcher, identity, env, options = {}) {
46
+ if (identity.auth === 'cdp-attach') {
47
+ if (options.cdpEndpointUrl === undefined) {
48
+ throw new QaError('CDP_ENDPOINT_MISSING', 'No CDP endpoint URL was given for a "cdp-attach" identity', {
49
+ remediation: 'Start Chrome with --remote-debugging-port and pass its endpoint URL.',
50
+ });
51
+ }
52
+ return attachViaCdp(launcher, options.cdpEndpointUrl);
53
+ }
54
+ const password = env[identity.secret];
55
+ if (password === undefined || password === '') {
56
+ throw new QaError('IDENTITY_SECRET_MISSING', `Environment variable "${identity.secret}" is not set`, {
57
+ remediation: `Set ${identity.secret} before authenticating this identity.`,
58
+ });
59
+ }
60
+ // The schema requires loginUrl and username whenever auth is "storage-state" (packages/schemas
61
+ // IdentityConfigSchema); a config that reached this point without them is not one qa validate
62
+ // would have accepted, but the check stays for anyone constructing IdentityConfig by hand.
63
+ if (identity.loginUrl === undefined || identity.username === undefined) {
64
+ throw new QaError('LOGIN_CONFIG_INCOMPLETE', 'A "storage-state" identity needs both loginUrl and username', {
65
+ remediation: 'Set identities.<name>.loginUrl and identities.<name>.username in config.yaml.',
66
+ });
67
+ }
68
+ return loginWithCredentials(launcher, {
69
+ loginUrl: identity.loginUrl,
70
+ username: identity.username,
71
+ password,
72
+ ...(identity.selectors?.username !== undefined ? { usernameSelector: identity.selectors.username } : {}),
73
+ ...(identity.selectors?.password !== undefined ? { passwordSelector: identity.selectors.password } : {}),
74
+ ...(identity.selectors?.submit !== undefined ? { submitSelector: identity.selectors.submit } : {}),
75
+ });
76
+ }
77
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { IdentityConfig } from '@qa-ai-stlc/schemas';
2
+ import type { FileSystem } from './ports/file-system.js';
3
+ import type { HttpClient } from './ports/http-client.js';
4
+ import type { ProcessRunner } from './ports/process-runner.js';
5
+ export type DoctorCheckStatus = 'pass' | 'fail';
6
+ export interface DoctorCheckResult {
7
+ readonly name: string;
8
+ readonly status: DoctorCheckStatus;
9
+ readonly message: string;
10
+ readonly remediation?: string;
11
+ }
12
+ export declare const SUPPORTED_BROWSERS: readonly ["chromium", "firefox", "webkit"];
13
+ export type SupportedBrowser = (typeof SUPPORTED_BROWSERS)[number];
14
+ export declare function resolveBrowserExecutablePath(browser: SupportedBrowser): string;
15
+ /** Checks the running Node version against the framework's documented floor (ADR-004). */
16
+ export declare function checkNodeVersion(currentVersion?: string): DoctorCheckResult;
17
+ /** Checks whether a browser Playwright owns (ADR-004) is actually present on disk. */
18
+ export declare function checkBrowserInstalled(fs: FileSystem, browser: SupportedBrowser): Promise<DoctorCheckResult>;
19
+ export interface CheckBaseUrlOptions {
20
+ readonly httpClient?: HttpClient;
21
+ readonly timeoutMs?: number;
22
+ }
23
+ /** Checks that the configured environment's base URL responds at all (ADR-004, section 2.7 step 2). */
24
+ export declare function checkBaseUrlReachable(baseUrl: string, options?: CheckBaseUrlOptions): Promise<DoctorCheckResult>;
25
+ /** Checks that every configured identity's secret environment variable is actually set. */
26
+ export declare function checkIdentitiesPresent(identities: Readonly<Record<string, IdentityConfig>>, env: Readonly<Record<string, string | undefined>>): readonly DoctorCheckResult[];
27
+ /** `qa doctor --fix`: installs the given browsers through Playwright's own installer. */
28
+ export declare function installBrowsers(processRunner: ProcessRunner, browsers?: readonly SupportedBrowser[]): Promise<DoctorCheckResult>;
29
+ //# sourceMappingURL=browser-doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-doctor.d.ts","sourceRoot":"","sources":["../src/browser-doctor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,kBAAkB,4CAA6C,CAAC;AAC7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAQnE,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAE9E;AA8BD,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,cAAc,GAAE,MAAwB,GAAG,iBAAiB,CAwB5F;AAED,sFAAsF;AACtF,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,iBAAiB,CAAC,CAW5B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,uGAAuG;AACvG,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CAoB5B;AAED,2FAA2F;AAC3F,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,EACpD,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAChD,SAAS,iBAAiB,EAAE,CAY9B;AAQD,yFAAyF;AACzF,wBAAsB,eAAe,CACnC,aAAa,EAAE,aAAa,EAC5B,QAAQ,GAAE,SAAS,gBAAgB,EAAuB,GACzD,OAAO,CAAC,iBAAiB,CAAC,CAY5B"}
@@ -0,0 +1,130 @@
1
+ // Copyright The QA-AI-STLC Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { createRequire } from 'node:module';
4
+ import { dirname, join } from 'node:path';
5
+ import { chromium, firefox, webkit } from 'playwright';
6
+ import { fetchHttpClient } from './ports/http-client.js';
7
+ export const SUPPORTED_BROWSERS = ['chromium', 'firefox', 'webkit'];
8
+ const EXECUTABLE_PATH_RESOLVERS = {
9
+ chromium: () => chromium.executablePath(),
10
+ firefox: () => firefox.executablePath(),
11
+ webkit: () => webkit.executablePath(),
12
+ };
13
+ export function resolveBrowserExecutablePath(browser) {
14
+ return EXECUTABLE_PATH_RESOLVERS[browser]();
15
+ }
16
+ // Keep in sync with the root package.json "engines.node" floor (AGENTS.md 5.1); there is no
17
+ // single runtime source both a packaged CLI and this check can share.
18
+ const MIN_NODE_VERSION = { major: 22, minor: 12, patch: 0 };
19
+ function parseVersion(version) {
20
+ const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version);
21
+ if (!match?.[1] || !match[2] || !match[3]) {
22
+ return undefined;
23
+ }
24
+ return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
25
+ }
26
+ function isAtLeast(version, floor) {
27
+ if (version.major !== floor.major) {
28
+ return version.major > floor.major;
29
+ }
30
+ if (version.minor !== floor.minor) {
31
+ return version.minor > floor.minor;
32
+ }
33
+ return version.patch >= floor.patch;
34
+ }
35
+ /** Checks the running Node version against the framework's documented floor (ADR-004). */
36
+ export function checkNodeVersion(currentVersion = process.version) {
37
+ const floorLabel = [MIN_NODE_VERSION.major, MIN_NODE_VERSION.minor, MIN_NODE_VERSION.patch].join('.');
38
+ const parsed = parseVersion(currentVersion);
39
+ if (parsed === undefined) {
40
+ return {
41
+ name: 'node-version',
42
+ status: 'fail',
43
+ message: `Could not parse Node version "${currentVersion}"`,
44
+ remediation: `Install Node >=${floorLabel}.`,
45
+ };
46
+ }
47
+ if (!isAtLeast(parsed, MIN_NODE_VERSION)) {
48
+ return {
49
+ name: 'node-version',
50
+ status: 'fail',
51
+ message: `Node ${currentVersion} is older than the required ${floorLabel}`,
52
+ remediation: `Install Node >=${floorLabel}.`,
53
+ };
54
+ }
55
+ return {
56
+ name: 'node-version',
57
+ status: 'pass',
58
+ message: `Node ${currentVersion} satisfies >=${floorLabel}`,
59
+ };
60
+ }
61
+ /** Checks whether a browser Playwright owns (ADR-004) is actually present on disk. */
62
+ export async function checkBrowserInstalled(fs, browser) {
63
+ const executablePath = resolveBrowserExecutablePath(browser);
64
+ const installed = await fs.pathExists(executablePath);
65
+ return installed
66
+ ? { name: `browser:${browser}`, status: 'pass', message: `${browser} is installed` }
67
+ : {
68
+ name: `browser:${browser}`,
69
+ status: 'fail',
70
+ message: `${browser} is not installed`,
71
+ remediation: `Run "qa doctor --fix" to install it, or "npx playwright install ${browser}".`,
72
+ };
73
+ }
74
+ /** Checks that the configured environment's base URL responds at all (ADR-004, section 2.7 step 2). */
75
+ export async function checkBaseUrlReachable(baseUrl, options = {}) {
76
+ const httpClient = options.httpClient ?? fetchHttpClient;
77
+ const timeoutMs = options.timeoutMs ?? 5000;
78
+ const controller = new AbortController();
79
+ const timeout = setTimeout(() => {
80
+ controller.abort();
81
+ }, timeoutMs);
82
+ try {
83
+ await httpClient.get(baseUrl, { signal: controller.signal });
84
+ return { name: 'base-url', status: 'pass', message: `${baseUrl} is reachable` };
85
+ }
86
+ catch {
87
+ return {
88
+ name: 'base-url',
89
+ status: 'fail',
90
+ message: `${baseUrl} is not reachable`,
91
+ remediation: 'Check the environment is running and the configured baseUrl is correct.',
92
+ };
93
+ }
94
+ finally {
95
+ clearTimeout(timeout);
96
+ }
97
+ }
98
+ /** Checks that every configured identity's secret environment variable is actually set. */
99
+ export function checkIdentitiesPresent(identities, env) {
100
+ return Object.entries(identities).map(([name, identity]) => {
101
+ const value = env[identity.secret];
102
+ return value !== undefined && value !== ''
103
+ ? { name: `identity:${name}`, status: 'pass', message: `${identity.secret} is set` }
104
+ : {
105
+ name: `identity:${name}`,
106
+ status: 'fail',
107
+ message: `${identity.secret} is not set`,
108
+ remediation: `Set the ${identity.secret} environment variable before using identity "${name}".`,
109
+ };
110
+ });
111
+ }
112
+ function resolvePlaywrightCliPath() {
113
+ const require = createRequire(import.meta.url);
114
+ const packageJsonPath = require.resolve('playwright/package.json');
115
+ return join(dirname(packageJsonPath), 'cli.js');
116
+ }
117
+ /** `qa doctor --fix`: installs the given browsers through Playwright's own installer. */
118
+ export async function installBrowsers(processRunner, browsers = SUPPORTED_BROWSERS) {
119
+ const cliPath = resolvePlaywrightCliPath();
120
+ const result = await processRunner.run(process.execPath, [cliPath, 'install', ...browsers]);
121
+ return result.exitCode === 0
122
+ ? { name: 'browser-install', status: 'pass', message: `Installed: ${browsers.join(', ')}` }
123
+ : {
124
+ name: 'browser-install',
125
+ status: 'fail',
126
+ message: `Installing browsers failed with exit code ${String(result.exitCode)}`,
127
+ remediation: result.stderr.trim() || 'Run "npx playwright install" manually and check network access.',
128
+ };
129
+ }
130
+ //# sourceMappingURL=browser-doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-doctor.js","sourceRoot":"","sources":["../src/browser-doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { type Config } from '@qa-ai-stlc/schemas';
2
+ import type { QaStore } from './qa-store.js';
3
+ /**
4
+ * Reads and validates `.qa/config.yaml` against `ConfigSchema`, the schema-versioned contract
5
+ * every other package trusts. Throws `QaError` with a stable `code` and a remediation an agent
6
+ * or CLI flag can act on, never a bare Zod or YAML parse error (AGENTS.md 5.4).
7
+ */
8
+ export declare function loadConfig(store: QaStore): Promise<Config>;
9
+ //# sourceMappingURL=config-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../src/config-loader.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,KAAK,MAAM,EAAqB,MAAM,qBAAqB,CAAC;AAGnF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CA+BhE"}
@@ -0,0 +1,40 @@
1
+ // Copyright The QA-AI-STLC Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { z } from 'zod';
4
+ import { ConfigSchema } from '@qa-ai-stlc/schemas';
5
+ import { parse as parseYaml } from 'yaml';
6
+ import { QaError } from './errors.js';
7
+ const CONFIG_PATH = 'config.yaml';
8
+ /**
9
+ * Reads and validates `.qa/config.yaml` against `ConfigSchema`, the schema-versioned contract
10
+ * every other package trusts. Throws `QaError` with a stable `code` and a remediation an agent
11
+ * or CLI flag can act on, never a bare Zod or YAML parse error (AGENTS.md 5.4).
12
+ */
13
+ export async function loadConfig(store) {
14
+ const exists = await store.pathExists(CONFIG_PATH);
15
+ if (!exists) {
16
+ throw new QaError('CONFIG_MISSING', 'No .qa/config.yaml found', {
17
+ remediation: 'Run "qa init" to create the .qa/ store and its configuration file.',
18
+ });
19
+ }
20
+ const raw = await store.readText(CONFIG_PATH);
21
+ let parsed;
22
+ try {
23
+ parsed = parseYaml(raw);
24
+ }
25
+ catch (error) {
26
+ throw new QaError('CONFIG_MALFORMED', '.qa/config.yaml is not valid YAML', {
27
+ remediation: 'Fix the YAML syntax reported below and re-run.',
28
+ cause: error,
29
+ });
30
+ }
31
+ const result = ConfigSchema.safeParse(parsed);
32
+ if (!result.success) {
33
+ throw new QaError('CONFIG_INVALID', `.qa/config.yaml does not match its schema:\n${z.prettifyError(result.error)}`, {
34
+ remediation: 'Fix the reported fields or regenerate the file with "qa init".',
35
+ cause: result.error,
36
+ });
37
+ }
38
+ return result.data;
39
+ }
40
+ //# sourceMappingURL=config-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../src/config-loader.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAEtC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAkC,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGtC,MAAM,WAAW,GAAiB,aAAa,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAc;IAC7C,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,OAAO,CAAC,gBAAgB,EAAE,0BAA0B,EAAE;YAC9D,WAAW,EAAE,oEAAoE;SAClF,CAAC,CAAC;IACL,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC9C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,OAAO,CAAC,kBAAkB,EAAE,mCAAmC,EAAE;YACzE,WAAW,EAAE,gDAAgD;YAC7D,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,OAAO,CACf,gBAAgB,EAChB,+CAA+C,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAC9E;YACE,WAAW,EAAE,gEAAgE;YAC7E,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CACF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface QaErrorOptions {
2
+ readonly remediation?: string;
3
+ readonly cause?: unknown;
4
+ }
5
+ /**
6
+ * Every expected engine failure (bad config, hash mismatch, unregistered artifact) is a
7
+ * `QaError` with a stable, machine-readable `code` an agent or CLI flag can branch on, and an
8
+ * optional `remediation` it can act on or show the operator (AGENTS.md 5.4).
9
+ */
10
+ export declare class QaError extends Error {
11
+ readonly code: string;
12
+ readonly remediation: string | undefined;
13
+ constructor(code: string, message: string, options?: QaErrorOptions);
14
+ }
15
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,OAAQ,SAAQ,KAAK;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE7B,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB;CAMxE"}
package/dist/errors.js ADDED
@@ -0,0 +1,18 @@
1
+ // Copyright The QA-AI-STLC Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Every expected engine failure (bad config, hash mismatch, unregistered artifact) is a
5
+ * `QaError` with a stable, machine-readable `code` an agent or CLI flag can branch on, and an
6
+ * optional `remediation` it can act on or show the operator (AGENTS.md 5.4).
7
+ */
8
+ export class QaError extends Error {
9
+ code;
10
+ remediation;
11
+ constructor(code, message, options = {}) {
12
+ super(message, options.cause !== undefined ? { cause: options.cause } : undefined);
13
+ this.name = 'QaError';
14
+ this.code = code;
15
+ this.remediation = options.remediation;
16
+ }
17
+ }
18
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAOtC;;;;GAIG;AACH,MAAM,OAAO,OAAQ,SAAQ,KAAK;IACvB,IAAI,CAAS;IACb,WAAW,CAAqB;IAEzC,YAAY,IAAY,EAAE,OAAe,EAAE,UAA0B,EAAE;QACrE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACnF,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACzC,CAAC;CACF"}
@@ -0,0 +1,43 @@
1
+ import { type Evidence, type EvidenceKind, type Identifier, type RelativePath } from '@qa-ai-stlc/schemas';
2
+ import { type Clock } from './ports/clock.js';
3
+ import type { ManifestStore } from './manifest-store.js';
4
+ import type { QaStore } from './qa-store.js';
5
+ export interface EvidenceRegisterOptions {
6
+ readonly id: Identifier;
7
+ readonly runId: Identifier;
8
+ readonly stepId?: Identifier;
9
+ readonly kind: EvidenceKind;
10
+ readonly content: string | Uint8Array;
11
+ /** Without a leading dot. Defaults to a sensible extension for `kind`. */
12
+ readonly fileExtension?: string;
13
+ }
14
+ export type EvidenceRegistration = {
15
+ readonly status: 'registered';
16
+ readonly evidence: Evidence;
17
+ } | {
18
+ readonly status: 'quarantined';
19
+ readonly receiptPath: RelativePath;
20
+ readonly patterns: readonly string[];
21
+ };
22
+ export interface EvidenceStoreOptions {
23
+ readonly store: QaStore;
24
+ readonly manifest: ManifestStore;
25
+ readonly clock?: Clock;
26
+ }
27
+ /**
28
+ * Registers evidence the way AGENTS.md 2.5/12.5 require: hashed, timestamped, linked to a step,
29
+ * and scanned for leaked secrets before anything is written to disk. `network-har` content is
30
+ * redacted proactively (development plan section 6.4); the secret scan is the safety net for
31
+ * everything else, or for whatever that redaction pass could not understand. Content that still
32
+ * matches a known secret shape after redaction is never written — a sanitized quarantine receipt
33
+ * is registered in its place instead (AGENTS.md 12.5).
34
+ */
35
+ export declare class EvidenceStore {
36
+ private readonly store;
37
+ private readonly manifest;
38
+ private readonly clock;
39
+ constructor(options: EvidenceStoreOptions);
40
+ register(options: EvidenceRegisterOptions): Promise<EvidenceRegistration>;
41
+ private writeQuarantineReceipt;
42
+ }
43
+ //# sourceMappingURL=evidence-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence-store.d.ts","sourceRoot":"","sources":["../src/evidence-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAW7C,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IACtC,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAC9D;IACE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC;AAEN,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;gBAElB,OAAO,EAAE,oBAAoB;IAMnC,QAAQ,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,oBAAoB,CAAC;YAqCjE,sBAAsB;CAkBrC"}
@@ -0,0 +1,81 @@
1
+ // Copyright The QA-AI-STLC Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { EvidenceQuarantineReceiptSchema, EvidenceSchema, } from '@qa-ai-stlc/schemas';
4
+ import { hashContent } from './hash.js';
5
+ import { toCanonicalJson } from './json-file.js';
6
+ import { systemClock } from './ports/clock.js';
7
+ import { redactHar } from './redaction.js';
8
+ import { scanForSecrets } from './secret-scan.js';
9
+ const DEFAULT_FILE_EXTENSIONS = {
10
+ screenshot: 'png',
11
+ trace: 'zip',
12
+ 'network-har': 'har',
13
+ 'console-log': 'log',
14
+ video: 'webm',
15
+ other: 'bin',
16
+ };
17
+ /**
18
+ * Registers evidence the way AGENTS.md 2.5/12.5 require: hashed, timestamped, linked to a step,
19
+ * and scanned for leaked secrets before anything is written to disk. `network-har` content is
20
+ * redacted proactively (development plan section 6.4); the secret scan is the safety net for
21
+ * everything else, or for whatever that redaction pass could not understand. Content that still
22
+ * matches a known secret shape after redaction is never written — a sanitized quarantine receipt
23
+ * is registered in its place instead (AGENTS.md 12.5).
24
+ */
25
+ export class EvidenceStore {
26
+ store;
27
+ manifest;
28
+ clock;
29
+ constructor(options) {
30
+ this.store = options.store;
31
+ this.manifest = options.manifest;
32
+ this.clock = options.clock ?? systemClock;
33
+ }
34
+ async register(options) {
35
+ const extension = options.fileExtension ?? DEFAULT_FILE_EXTENSIONS[options.kind];
36
+ let content = options.content;
37
+ let redacted = false;
38
+ if (options.kind === 'network-har' && typeof content === 'string') {
39
+ const result = redactHar(content);
40
+ content = result.content;
41
+ redacted = result.redacted;
42
+ }
43
+ const textForScan = typeof content === 'string' ? content : Buffer.from(content).toString('latin1');
44
+ const matches = scanForSecrets(textForScan);
45
+ if (matches.length > 0) {
46
+ const patterns = matches.map((match) => match.pattern);
47
+ const receiptPath = await this.writeQuarantineReceipt(options, patterns);
48
+ return { status: 'quarantined', receiptPath, patterns };
49
+ }
50
+ const relativePath = `evidence/${options.runId}/${options.id}.${extension}`;
51
+ await this.store.writeBytes(relativePath, content);
52
+ await this.manifest.register(relativePath, content);
53
+ const evidence = EvidenceSchema.parse({
54
+ id: options.id,
55
+ runId: options.runId,
56
+ ...(options.stepId !== undefined ? { stepId: options.stepId } : {}),
57
+ kind: options.kind,
58
+ path: relativePath,
59
+ sha256: hashContent(content),
60
+ createdAt: this.clock.now().toISOString(),
61
+ redacted,
62
+ });
63
+ return { status: 'registered', evidence };
64
+ }
65
+ async writeQuarantineReceipt(options, patterns) {
66
+ const receiptPath = `evidence/${options.runId}/${options.id}.quarantine.json`;
67
+ const receipt = EvidenceQuarantineReceiptSchema.parse({
68
+ id: options.id,
69
+ runId: options.runId,
70
+ ...(options.stepId !== undefined ? { stepId: options.stepId } : {}),
71
+ kind: options.kind,
72
+ createdAt: this.clock.now().toISOString(),
73
+ reason: 'secret-detected',
74
+ patterns,
75
+ });
76
+ await this.store.writeJson(receiptPath, receipt);
77
+ await this.manifest.register(receiptPath, toCanonicalJson(receipt));
78
+ return receiptPath;
79
+ }
80
+ }
81
+ //# sourceMappingURL=evidence-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence-store.js","sourceRoot":"","sources":["../src/evidence-store.ts"],"names":[],"mappings":"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"}
package/dist/hash.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { Sha256Hex } from '@qa-ai-stlc/schemas';
2
+ export declare function hashText(content: string): Sha256Hex;
3
+ export declare function hashBytes(content: Uint8Array): Sha256Hex;
4
+ /** Picks the text or binary hasher based on the content's runtime type. */
5
+ export declare function hashContent(content: string | Uint8Array): Sha256Hex;
6
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAIrD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAGnD;AAID,wBAAgB,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAExD;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAEnE"}