ai-saas-guard 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,8 +73,8 @@ The CLI is published on npm as `ai-saas-guard`, and the GitHub Action is availab
73
73
  | JSON and SARIF output | Available |
74
74
  | Composite GitHub Action | Available |
75
75
  | Project config | `.ai-saas-guard.json` rule toggles, severity overrides, and fail thresholds |
76
- | Versioned Action tags | `v0.14.0`, `v0` |
77
- | npm package | `ai-saas-guard@0.14.0` |
76
+ | Versioned Action tags | `v0.16.0`, `v0` |
77
+ | npm package | `ai-saas-guard@0.16.0` |
78
78
  | npm publishing | Trusted Publisher/OIDC, no long-lived publish token |
79
79
 
80
80
  ## Quick Start
@@ -208,19 +208,19 @@ The first hosted service slice is defined in [docs/hosted-first-service-slice.md
208
208
 
209
209
  The hosted deployment model is documented in [docs/hosted-deployment-model.md](docs/hosted-deployment-model.md). It chooses a containerized Node.js ingress and worker model with a managed durable queue, platform secret manager, structured redacted logs, installation/repository rate limits, and rollback/incident response paths.
210
210
 
211
- The hosted operational release gate is documented in [docs/hosted-operational-release-gate.md](docs/hosted-operational-release-gate.md). It defines the hosted-specific CI, replay, queue, worker cleanup, privacy, monitoring, rollback, and incident-response evidence required before any hosted environment is exposed to users.
211
+ The hosted operational release gate is documented in [docs/hosted-operational-release-gate.md](docs/hosted-operational-release-gate.md). It defines the hosted-specific CI, replay, queue, worker cleanup, privacy, monitoring, rollback, and incident-response evidence required before any hosted environment is exposed to users. The pure gate evaluator exported from `ai-saas-guard/hosted/contracts` blocks hosted exposure unless every P0 evidence item is fresh, a container digest is recorded, and release notes avoid pentest, certification, and full-audit claims.
212
212
 
213
213
  Hosted uninstall and data deletion behavior is documented in [docs/hosted-uninstall-data-deletion.md](docs/hosted-uninstall-data-deletion.md). It defines repository removal, full app uninstall, compact report deletion, queue cancellation, audit record retention, repeated cleanup, and user-facing deletion wording.
214
214
 
215
215
  Hosted pricing and packaging boundaries are documented in [docs/hosted-pricing-packaging.md](docs/hosted-pricing-packaging.md). Core local scanning stays useful without an account; hosted plans may add workflow convenience, saved reports, team policy, and optional human review, but they do not gate local CLI scanning.
216
216
 
217
- Hosted pre-implementation pure contracts are documented in [docs/hosted-preimplementation-contracts.md](docs/hosted-preimplementation-contracts.md). They now include a pull request webhook intake planner that verifies signatures before parsing or queueing, a durable scan queue planner that reuses queued, running, and completed jobs for the same trusted scan key, a worker read-only scan planner that fixes the CLI command and requires repository `contents: read`, and a Check Run publication planner that requires repository `checks: write` and builds bounded check-only payloads from compact reports. They also cover queue-safe webhook event parsing, bounded check-run summary rendering, idempotent queue cleanup planning, worker checkout cleanup planning, and other service-free helpers exported from `ai-saas-guard/hosted/contracts`. PR comments remain a later workflow or paid hosted feature, not part of the hosted MVP contract.
217
+ Hosted pre-implementation pure contracts are documented in [docs/hosted-preimplementation-contracts.md](docs/hosted-preimplementation-contracts.md). They now include a pull request webhook intake planner that verifies signatures before parsing or queueing, a durable scan queue planner that reuses queued, running, and completed jobs for the same trusted scan key, a worker read-only scan planner that fixes the CLI command and requires repository `contents: read`, and a Check Run publication planner that requires repository `checks: write` and builds bounded check-only payloads from compact reports. They also cover queue-safe webhook event parsing, bounded check-run summary rendering, idempotent queue cleanup planning, worker checkout cleanup planning, a retention/deletion cleanup planner, and an operational release gate evaluator that requires fresh CI, replay, static-check, dependency, container, cleanup, privacy, monitoring, rollback, incident-response, and release-cleanup evidence before hosted exposure. PR comments remain a later workflow or paid hosted feature, not part of the hosted MVP contract.
218
218
 
219
219
  A public hosted compact report schema fixture is available at [examples/hosted-compact-report.json](examples/hosted-compact-report.json). It is synthetic and public-safe: compact evidence only, no raw source, raw diffs, secrets, webhook payload bodies, customer payloads, private URLs, or worker checkout paths.
220
220
 
221
221
  The proposed hosted app permission boundary is intentionally narrow: repository contents read, pull requests read, checks write, and metadata read for the first version. Optional PR comments require repository policy opt-in, and broad permissions such as administration, deployments, Actions write, and repository secrets are out of scope.
222
222
 
223
- The repository also includes pure pre-implementation hosted contract helpers and tests for webhook intake order, webhook verification, installation token scoping, durable queue idempotency, compact reports, and retention limits. These helpers do not implement or deploy a hosted service.
223
+ The repository also includes pure pre-implementation hosted contract helpers and tests for webhook intake order, webhook verification, installation token scoping, durable queue idempotency, compact reports, retention limits, uninstall cleanup, repeated cleanup idempotency, scoped deletion planning, and operational release gate blocking. These helpers do not implement or deploy a hosted service.
224
224
 
225
225
  Users should prefer the local CLI for private repositories, offline review, or no-account workflows where hosted code processing is not acceptable.
226
226
 
@@ -254,7 +254,7 @@ Use `suppressions` for narrower false-positive handling when one rule is noisy o
254
254
 
255
255
  ## GitHub Action
256
256
 
257
- The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.14.0` for controlled upgrades, or pin a reviewed commit SHA for stricter supply-chain control:
257
+ The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.16.0` for controlled upgrades, or pin a reviewed commit SHA for stricter supply-chain control:
258
258
 
259
259
  ```yaml
260
260
  name: ai-saas-guard
package/README.zh-CN.md CHANGED
@@ -55,7 +55,7 @@ AI 能很快把一个 SaaS 从想法做成可运行的产品。真正难的是
55
55
 
56
56
  这个仓库是公开 GitHub 仓库。
57
57
 
58
- CLI 已发布到 npm:`ai-saas-guard@0.14.0`。GitHub Action 支持 `v0` 浮动标签,也支持固定版本标签,例如 `v0.14.0`。
58
+ CLI 已发布到 npm:`ai-saas-guard@0.16.0`。GitHub Action 支持 `v0` 浮动标签,也支持固定版本标签,例如 `v0.16.0`。
59
59
 
60
60
  | 模块 | 状态 |
61
61
  | --- | --- |
@@ -66,8 +66,8 @@ CLI 已发布到 npm:`ai-saas-guard@0.14.0`。GitHub Action 支持 `v0` 浮动
66
66
  | Markdown PR summary | 已可用 |
67
67
  | GitHub Action | 已可用 |
68
68
  | 项目配置 | `.ai-saas-guard.json` 支持规则开关、severity 覆盖和 fail threshold |
69
- | 当前版本 | `0.14.0` |
70
- | Action 标签 | `v0.14.0`、`v0` |
69
+ | 当前版本 | `0.16.0` |
70
+ | Action 标签 | `v0.16.0`、`v0` |
71
71
  | npm 发布 | GitHub Actions Trusted Publisher/OIDC,无需长期 npm token |
72
72
 
73
73
  ## 快速开始
@@ -260,6 +260,8 @@ jobs:
260
260
  - Check Run publication planner:要求 repository `checks: write`,只从 compact report 生成有长度上限的 Check Run payload,包含 review categories、优先 review 文件、verification steps 和本地 CLI 复现命令;MVP 不发 PR comment
261
261
  - queue cleanup planner
262
262
  - worker checkout cleanup planner
263
+ - retention/deletion cleanup planner:把 compact report 删除、按仓库或 installation 范围取消队列和 running job、worker checkout 删除、retention 过期清理、最小审计记录合成一个安全计划;不会输出源码、diff、secret、customer payload、private URL、checkout path 或底层 cleanup error
264
+ - operational release gate evaluator:检查 hosted 暴露前是否具备 fresh CI、webhook replay、workflow static check、dependency/container scan、cleanup、privacy、monitoring、rollback、incident response 和 release cleanup 证据;缺任何 P0 证据都会阻止 hosted exposure
263
265
  - hosted compact report fixture:[examples/hosted-compact-report.json](examples/hosted-compact-report.json)
264
266
 
265
267
  这些 helper 不会启动服务、不会调用 GitHub API、不会请求 installation token、不会真实写 check run、不会发 PR comment,也不会上传源码。
@@ -481,6 +481,187 @@ export interface HostedDeletionPlan {
481
481
  auditRecordRetentionDays: number;
482
482
  visibleUserMessage: string;
483
483
  }
484
+ export type HostedRetentionAndDeletionCleanupTrigger = HostedDeletionTrigger | "retention_expired";
485
+ export interface HostedCompactReportRetentionRecord {
486
+ id: string;
487
+ installationId: number;
488
+ repositoryId: number;
489
+ createdAt: string;
490
+ retentionDays?: number;
491
+ expiresAt?: string;
492
+ }
493
+ export interface HostedWorkerCheckoutCleanupState {
494
+ key: string;
495
+ identity: HostedScanIdentity;
496
+ status?: "active" | "deletion_pending" | "deleted";
497
+ }
498
+ export interface HostedRetentionAndDeletionCleanupInput {
499
+ trigger: HostedRetentionAndDeletionCleanupTrigger;
500
+ installationId: number;
501
+ repositoryId?: number;
502
+ requestedAt: string;
503
+ compactReports: HostedCompactReportRetentionRecord[];
504
+ jobs: HostedQueueCleanupJobState[];
505
+ workerCheckouts: HostedWorkerCheckoutCleanupState[];
506
+ auditRecordRetentionDays?: number;
507
+ rawSource?: string;
508
+ rawDiff?: string;
509
+ secretValues?: string[];
510
+ customerPayload?: unknown;
511
+ }
512
+ export interface HostedRetentionAndDeletionCleanupAuditRecord {
513
+ cleanupRequestId: string;
514
+ trigger: HostedRetentionAndDeletionCleanupTrigger;
515
+ status: "planned";
516
+ installationId: number;
517
+ repositoryId?: number;
518
+ requestedAt: string;
519
+ }
520
+ export interface HostedRetentionAndDeletionCleanupPlan {
521
+ trigger: HostedRetentionAndDeletionCleanupTrigger;
522
+ scope: "repository" | "installation";
523
+ installationId: number;
524
+ repositoryId?: number;
525
+ requestedAt: string;
526
+ idempotencyKey: string;
527
+ idempotent: true;
528
+ deleteCompactReportIds: string[];
529
+ preserveCompactReportIds: string[];
530
+ cancelQueuedJobKeys: string[];
531
+ requestRunningCancellationJobKeys: string[];
532
+ preserveTerminalJobKeys: string[];
533
+ keepUnmatchedJobKeys: string[];
534
+ deleteWorkerCheckoutKeys: string[];
535
+ keepWorkerCheckoutKeys: string[];
536
+ deleteCompactReports: true;
537
+ cancelQueuedJobs: boolean;
538
+ requestRunningCancellation: boolean;
539
+ deleteWorkerCheckouts: boolean;
540
+ shouldFetchRepository: false;
541
+ shouldRequeueScans: false;
542
+ deleteRawSource: false;
543
+ deleteRawDiffs: false;
544
+ deleteSecrets: false;
545
+ deleteCustomerPayloads: false;
546
+ deleteGitHubOwnedCheckRuns: false;
547
+ preserveAuditRecord: true;
548
+ auditRecordRetentionDays: number;
549
+ auditRecord: HostedRetentionAndDeletionCleanupAuditRecord;
550
+ visibleUserMessage: string;
551
+ privacy: {
552
+ includesRawSource: false;
553
+ includesRawDiffs: false;
554
+ includesSecrets: false;
555
+ includesCustomerPayloads: false;
556
+ includesWorkerCheckoutPaths: false;
557
+ includesPrivateUrls: false;
558
+ includesLowLevelCleanupErrors: false;
559
+ };
560
+ }
561
+ export declare const HOSTED_OPERATIONAL_RELEASE_GATE_REQUIREMENTS: readonly [{
562
+ readonly id: "clean_ci";
563
+ readonly priority: "P0";
564
+ readonly label: "Clean CI";
565
+ }, {
566
+ readonly id: "hosted_contract_tests";
567
+ readonly priority: "P0";
568
+ readonly label: "Hosted contract tests";
569
+ }, {
570
+ readonly id: "webhook_replay";
571
+ readonly priority: "P0";
572
+ readonly label: "Webhook replay";
573
+ }, {
574
+ readonly id: "workflow_static_checks";
575
+ readonly priority: "P0";
576
+ readonly label: "Workflow static checks";
577
+ }, {
578
+ readonly id: "dependency_scan";
579
+ readonly priority: "P0";
580
+ readonly label: "Dependency scan";
581
+ }, {
582
+ readonly id: "container_scan";
583
+ readonly priority: "P0";
584
+ readonly label: "Container scan";
585
+ }, {
586
+ readonly id: "queue_worker_cleanup";
587
+ readonly priority: "P0";
588
+ readonly label: "Queue and worker cleanup";
589
+ }, {
590
+ readonly id: "privacy_retention";
591
+ readonly priority: "P0";
592
+ readonly label: "Privacy and retention";
593
+ }, {
594
+ readonly id: "monitoring_alerting";
595
+ readonly priority: "P0";
596
+ readonly label: "Monitoring and alerting";
597
+ }, {
598
+ readonly id: "manual_rollback";
599
+ readonly priority: "P0";
600
+ readonly label: "Manual rollback";
601
+ }, {
602
+ readonly id: "incident_response";
603
+ readonly priority: "P0";
604
+ readonly label: "Incident response";
605
+ }, {
606
+ readonly id: "release_cleanup";
607
+ readonly priority: "P0";
608
+ readonly label: "Release cleanup";
609
+ }];
610
+ export type HostedOperationalReleaseGateRequirementId = (typeof HOSTED_OPERATIONAL_RELEASE_GATE_REQUIREMENTS)[number]["id"];
611
+ export type HostedOperationalReleaseGateEvidenceStatus = "passed" | "failed" | "missing" | "exception";
612
+ export interface HostedOperationalReleaseGateEvidence {
613
+ id: HostedOperationalReleaseGateRequirementId;
614
+ status: HostedOperationalReleaseGateEvidenceStatus;
615
+ collectedAt?: string;
616
+ evidenceUrl?: string;
617
+ note?: string;
618
+ owner?: string;
619
+ }
620
+ export interface HostedOperationalReleaseGateInput {
621
+ commitSha: string;
622
+ scannerVersion: string;
623
+ deploymentTarget: string;
624
+ evaluatedAt: string;
625
+ evidence: HostedOperationalReleaseGateEvidence[];
626
+ releaseNotes: string;
627
+ containerImageDigest?: string;
628
+ maxEvidenceAgeDays?: number;
629
+ rawSource?: string;
630
+ rawDiff?: string;
631
+ secretValues?: string[];
632
+ customerPayload?: unknown;
633
+ }
634
+ export interface HostedOperationalReleaseGateDecision {
635
+ commitSha: string;
636
+ scannerVersion: string;
637
+ deploymentTarget: string;
638
+ evaluatedAt: string;
639
+ requiredEvidenceCount: number;
640
+ requiredEvidenceIds: HostedOperationalReleaseGateRequirementId[];
641
+ passedEvidenceIds: HostedOperationalReleaseGateRequirementId[];
642
+ missingEvidenceIds: HostedOperationalReleaseGateRequirementId[];
643
+ failedEvidenceIds: HostedOperationalReleaseGateRequirementId[];
644
+ staleEvidenceIds: HostedOperationalReleaseGateRequirementId[];
645
+ exceptionEvidenceIds: HostedOperationalReleaseGateRequirementId[];
646
+ containerImageDigestRecorded: boolean;
647
+ releaseNotesCompliant: boolean;
648
+ releaseNotesForbiddenClaims: string[];
649
+ shouldExposeHostedEnvironment: boolean;
650
+ blocked: boolean;
651
+ localCliBoundary: {
652
+ localCliUsableWithoutHostedService: true;
653
+ accountRequiredForLocalCli: false;
654
+ };
655
+ visibleUserMessage: string;
656
+ privacy: {
657
+ includesRawSource: false;
658
+ includesRawDiffs: false;
659
+ includesSecrets: false;
660
+ includesCustomerPayloads: false;
661
+ includesPrivateUrls: false;
662
+ modelTraining: "disabled";
663
+ };
664
+ }
484
665
  export declare const HOSTED_PRIVACY_DEFAULTS: {
485
666
  readonly retentionDays: 30;
486
667
  readonly auditRecordRetentionDays: 90;
@@ -515,3 +696,10 @@ export declare function getHostedDeletionIdempotencyKey(input: {
515
696
  repositoryId?: number;
516
697
  }): string;
517
698
  export declare function createHostedDeletionPlan(input: HostedDeletionPlanInput): HostedDeletionPlan;
699
+ export declare function getHostedRetentionAndDeletionCleanupIdempotencyKey(input: {
700
+ trigger: HostedRetentionAndDeletionCleanupTrigger;
701
+ installationId: number;
702
+ repositoryId?: number;
703
+ }): string;
704
+ export declare function planHostedRetentionAndDeletionCleanup(input: HostedRetentionAndDeletionCleanupInput): HostedRetentionAndDeletionCleanupPlan;
705
+ export declare function evaluateHostedOperationalReleaseGate(input: HostedOperationalReleaseGateInput): HostedOperationalReleaseGateDecision;
@@ -1,4 +1,18 @@
1
1
  import { createHmac, timingSafeEqual } from "node:crypto";
2
+ export const HOSTED_OPERATIONAL_RELEASE_GATE_REQUIREMENTS = [
3
+ { id: "clean_ci", priority: "P0", label: "Clean CI" },
4
+ { id: "hosted_contract_tests", priority: "P0", label: "Hosted contract tests" },
5
+ { id: "webhook_replay", priority: "P0", label: "Webhook replay" },
6
+ { id: "workflow_static_checks", priority: "P0", label: "Workflow static checks" },
7
+ { id: "dependency_scan", priority: "P0", label: "Dependency scan" },
8
+ { id: "container_scan", priority: "P0", label: "Container scan" },
9
+ { id: "queue_worker_cleanup", priority: "P0", label: "Queue and worker cleanup" },
10
+ { id: "privacy_retention", priority: "P0", label: "Privacy and retention" },
11
+ { id: "monitoring_alerting", priority: "P0", label: "Monitoring and alerting" },
12
+ { id: "manual_rollback", priority: "P0", label: "Manual rollback" },
13
+ { id: "incident_response", priority: "P0", label: "Incident response" },
14
+ { id: "release_cleanup", priority: "P0", label: "Release cleanup" }
15
+ ];
2
16
  export const HOSTED_PRIVACY_DEFAULTS = {
3
17
  retentionDays: 30,
4
18
  auditRecordRetentionDays: 90,
@@ -571,10 +585,160 @@ export function createHostedDeletionPlan(input) {
571
585
  deleteCustomerPayloads: false,
572
586
  deleteGitHubOwnedCheckRuns: false,
573
587
  preserveAuditRecord: true,
574
- auditRecordRetentionDays: input.auditRecordRetentionDays ?? HOSTED_PRIVACY_DEFAULTS.auditRecordRetentionDays,
588
+ auditRecordRetentionDays: resolveHostedAuditRecordRetentionDays(input.auditRecordRetentionDays),
575
589
  visibleUserMessage: "Hosted app-side compact reports and queued work are removed; GitHub-owned check runs remain in GitHub according to repository settings."
576
590
  };
577
591
  }
592
+ export function getHostedRetentionAndDeletionCleanupIdempotencyKey(input) {
593
+ return [
594
+ "retention-cleanup",
595
+ input.trigger,
596
+ input.installationId,
597
+ input.repositoryId ?? "all"
598
+ ].join(":");
599
+ }
600
+ export function planHostedRetentionAndDeletionCleanup(input) {
601
+ const retentionOnly = input.trigger === "retention_expired";
602
+ const scope = resolveRetentionAndDeletionCleanupScope(input);
603
+ const repositoryId = scope === "repository" ? input.repositoryId : undefined;
604
+ const idempotencyKey = getHostedRetentionAndDeletionCleanupIdempotencyKey({
605
+ trigger: input.trigger,
606
+ installationId: input.installationId,
607
+ repositoryId
608
+ });
609
+ const reportActions = planHostedCompactReportCleanup(input, scope);
610
+ const queueActions = input.trigger === "retention_expired"
611
+ ? emptyHostedQueueCleanupActions(input.jobs)
612
+ : createHostedQueueCleanupPlan({
613
+ trigger: input.trigger,
614
+ installationId: input.installationId,
615
+ repositoryId,
616
+ requestedAt: input.requestedAt,
617
+ jobs: input.jobs
618
+ });
619
+ const checkoutActions = retentionOnly
620
+ ? emptyHostedWorkerCheckoutCleanupActions(input.workerCheckouts)
621
+ : planHostedWorkerCheckoutCleanup(input, scope);
622
+ return {
623
+ trigger: input.trigger,
624
+ scope,
625
+ installationId: input.installationId,
626
+ ...(repositoryId === undefined ? {} : { repositoryId }),
627
+ requestedAt: input.requestedAt,
628
+ idempotencyKey,
629
+ idempotent: true,
630
+ deleteCompactReportIds: reportActions.deleteIds,
631
+ preserveCompactReportIds: reportActions.preserveIds,
632
+ cancelQueuedJobKeys: queueActions.cancelQueuedJobKeys,
633
+ requestRunningCancellationJobKeys: queueActions.requestRunningCancellationJobKeys,
634
+ preserveTerminalJobKeys: queueActions.preserveTerminalJobKeys,
635
+ keepUnmatchedJobKeys: queueActions.keepUnmatchedJobKeys,
636
+ deleteWorkerCheckoutKeys: checkoutActions.deleteKeys,
637
+ keepWorkerCheckoutKeys: checkoutActions.keepKeys,
638
+ deleteCompactReports: true,
639
+ cancelQueuedJobs: !retentionOnly,
640
+ requestRunningCancellation: !retentionOnly,
641
+ deleteWorkerCheckouts: !retentionOnly,
642
+ shouldFetchRepository: false,
643
+ shouldRequeueScans: false,
644
+ deleteRawSource: false,
645
+ deleteRawDiffs: false,
646
+ deleteSecrets: false,
647
+ deleteCustomerPayloads: false,
648
+ deleteGitHubOwnedCheckRuns: false,
649
+ preserveAuditRecord: true,
650
+ auditRecordRetentionDays: resolveHostedAuditRecordRetentionDays(input.auditRecordRetentionDays),
651
+ auditRecord: {
652
+ cleanupRequestId: idempotencyKey,
653
+ trigger: input.trigger,
654
+ status: "planned",
655
+ installationId: input.installationId,
656
+ ...(repositoryId === undefined ? {} : { repositoryId }),
657
+ requestedAt: input.requestedAt
658
+ },
659
+ visibleUserMessage: "Hosted app-side compact reports and queued work are removed; GitHub-owned check runs remain in GitHub according to repository settings.",
660
+ privacy: {
661
+ includesRawSource: false,
662
+ includesRawDiffs: false,
663
+ includesSecrets: false,
664
+ includesCustomerPayloads: false,
665
+ includesWorkerCheckoutPaths: false,
666
+ includesPrivateUrls: false,
667
+ includesLowLevelCleanupErrors: false
668
+ }
669
+ };
670
+ }
671
+ export function evaluateHostedOperationalReleaseGate(input) {
672
+ const requiredEvidenceIds = HOSTED_OPERATIONAL_RELEASE_GATE_REQUIREMENTS.map((requirement) => requirement.id);
673
+ const evidenceById = new Map(input.evidence.map((evidence) => [evidence.id, evidence]));
674
+ const maxEvidenceAgeDays = input.maxEvidenceAgeDays ?? 14;
675
+ const passedEvidenceIds = [];
676
+ const missingEvidenceIds = [];
677
+ const failedEvidenceIds = [];
678
+ const staleEvidenceIds = [];
679
+ const exceptionEvidenceIds = [];
680
+ for (const evidenceId of requiredEvidenceIds) {
681
+ const evidence = evidenceById.get(evidenceId);
682
+ if (!evidence || evidence.status === "missing" || !hasHostedGateEvidenceReference(evidence)) {
683
+ missingEvidenceIds.push(evidenceId);
684
+ continue;
685
+ }
686
+ if (evidence.status === "failed") {
687
+ failedEvidenceIds.push(evidenceId);
688
+ continue;
689
+ }
690
+ if (evidence.status === "exception") {
691
+ exceptionEvidenceIds.push(evidenceId);
692
+ continue;
693
+ }
694
+ if (!isHostedGateEvidenceFresh(evidence, input.evaluatedAt, maxEvidenceAgeDays)) {
695
+ staleEvidenceIds.push(evidenceId);
696
+ continue;
697
+ }
698
+ passedEvidenceIds.push(evidenceId);
699
+ }
700
+ const releaseNotesForbiddenClaims = findHostedReleaseNoteForbiddenClaims(input.releaseNotes);
701
+ const containerImageDigestRecorded = isHostedContainerImageDigest(input.containerImageDigest);
702
+ const blocked = missingEvidenceIds.length > 0 ||
703
+ failedEvidenceIds.length > 0 ||
704
+ staleEvidenceIds.length > 0 ||
705
+ exceptionEvidenceIds.length > 0 ||
706
+ releaseNotesForbiddenClaims.length > 0 ||
707
+ !containerImageDigestRecorded;
708
+ return {
709
+ commitSha: input.commitSha,
710
+ scannerVersion: input.scannerVersion,
711
+ deploymentTarget: input.deploymentTarget,
712
+ evaluatedAt: input.evaluatedAt,
713
+ requiredEvidenceCount: requiredEvidenceIds.length,
714
+ requiredEvidenceIds,
715
+ passedEvidenceIds,
716
+ missingEvidenceIds,
717
+ failedEvidenceIds,
718
+ staleEvidenceIds,
719
+ exceptionEvidenceIds,
720
+ containerImageDigestRecorded,
721
+ releaseNotesCompliant: releaseNotesForbiddenClaims.length === 0,
722
+ releaseNotesForbiddenClaims,
723
+ shouldExposeHostedEnvironment: !blocked,
724
+ blocked,
725
+ localCliBoundary: {
726
+ localCliUsableWithoutHostedService: true,
727
+ accountRequiredForLocalCli: false
728
+ },
729
+ visibleUserMessage: blocked
730
+ ? "Hosted exposure is blocked until every P0 gate has fresh evidence and release notes avoid pentest, certification, and full-audit claims."
731
+ : "Hosted exposure may proceed for this release candidate; keep the local CLI available without the hosted service.",
732
+ privacy: {
733
+ includesRawSource: false,
734
+ includesRawDiffs: false,
735
+ includesSecrets: false,
736
+ includesCustomerPayloads: false,
737
+ includesPrivateUrls: false,
738
+ modelTraining: HOSTED_PRIVACY_DEFAULTS.modelTraining
739
+ }
740
+ };
741
+ }
578
742
  function rejectWebhook(reason, deliveryId) {
579
743
  return {
580
744
  accepted: false,
@@ -714,6 +878,152 @@ function queueCleanupMatches(job, input, scope) {
714
878
  }
715
879
  return scope === "installation" || job.identity.repositoryId === input.repositoryId;
716
880
  }
881
+ function resolveRetentionAndDeletionCleanupScope(input) {
882
+ if (input.trigger === "installation_deleted") {
883
+ return "installation";
884
+ }
885
+ if (input.trigger === "retention_expired" && input.repositoryId === undefined) {
886
+ return "installation";
887
+ }
888
+ return "repository";
889
+ }
890
+ function planHostedCompactReportCleanup(input, scope) {
891
+ const deleteIds = [];
892
+ const preserveIds = [];
893
+ for (const report of input.compactReports) {
894
+ const matchesScope = retentionCleanupMatchesReport(report, input, scope);
895
+ const shouldDelete = matchesScope &&
896
+ (input.trigger !== "retention_expired" ||
897
+ isCompactReportExpired(report, input.requestedAt));
898
+ if (shouldDelete) {
899
+ deleteIds.push(report.id);
900
+ }
901
+ else {
902
+ preserveIds.push(report.id);
903
+ }
904
+ }
905
+ return { deleteIds, preserveIds };
906
+ }
907
+ function retentionCleanupMatchesReport(report, input, scope) {
908
+ if (report.installationId !== input.installationId) {
909
+ return false;
910
+ }
911
+ return scope === "installation" || report.repositoryId === input.repositoryId;
912
+ }
913
+ function isCompactReportExpired(report, requestedAt) {
914
+ const requestedAtMs = Date.parse(requestedAt);
915
+ if (!Number.isFinite(requestedAtMs)) {
916
+ return false;
917
+ }
918
+ if (report.expiresAt !== undefined) {
919
+ const expiresAtMs = Date.parse(report.expiresAt);
920
+ return Number.isFinite(expiresAtMs) && expiresAtMs <= requestedAtMs;
921
+ }
922
+ const createdAtMs = Date.parse(report.createdAt);
923
+ if (!Number.isFinite(createdAtMs)) {
924
+ return false;
925
+ }
926
+ const retentionDays = resolveHostedRetentionDays({
927
+ teamRequestedDays: report.retentionDays
928
+ });
929
+ return createdAtMs + retentionDays * 24 * 60 * 60 * 1000 <= requestedAtMs;
930
+ }
931
+ function emptyHostedQueueCleanupActions(jobs) {
932
+ return {
933
+ cancelQueuedJobKeys: [],
934
+ requestRunningCancellationJobKeys: [],
935
+ preserveTerminalJobKeys: [],
936
+ keepUnmatchedJobKeys: jobs.map((job) => job.key)
937
+ };
938
+ }
939
+ function planHostedWorkerCheckoutCleanup(input, scope) {
940
+ const deleteKeys = [];
941
+ const keepKeys = [];
942
+ for (const checkout of input.workerCheckouts) {
943
+ const matchesScope = retentionCleanupMatchesIdentity(checkout.identity, input, scope);
944
+ if (matchesScope && checkout.status !== "deleted") {
945
+ deleteKeys.push(checkout.key);
946
+ }
947
+ else {
948
+ keepKeys.push(checkout.key);
949
+ }
950
+ }
951
+ return { deleteKeys, keepKeys };
952
+ }
953
+ function emptyHostedWorkerCheckoutCleanupActions(workerCheckouts) {
954
+ return {
955
+ deleteKeys: [],
956
+ keepKeys: workerCheckouts.map((checkout) => checkout.key)
957
+ };
958
+ }
959
+ function retentionCleanupMatchesIdentity(identity, input, scope) {
960
+ if (identity.installationId !== input.installationId) {
961
+ return false;
962
+ }
963
+ return scope === "installation" || identity.repositoryId === input.repositoryId;
964
+ }
965
+ function resolveHostedAuditRecordRetentionDays(requestedDays) {
966
+ if (requestedDays === undefined) {
967
+ return HOSTED_PRIVACY_DEFAULTS.auditRecordRetentionDays;
968
+ }
969
+ return Math.min(HOSTED_PRIVACY_DEFAULTS.auditRecordRetentionDays, Math.max(1, Math.floor(requestedDays)));
970
+ }
971
+ function hasHostedGateEvidenceReference(evidence) {
972
+ return Boolean(evidence.evidenceUrl?.trim() || evidence.note?.trim());
973
+ }
974
+ function isHostedGateEvidenceFresh(evidence, evaluatedAt, maxEvidenceAgeDays) {
975
+ if (!evidence.collectedAt) {
976
+ return false;
977
+ }
978
+ const collectedAtMs = Date.parse(evidence.collectedAt);
979
+ const evaluatedAtMs = Date.parse(evaluatedAt);
980
+ if (!Number.isFinite(collectedAtMs) || !Number.isFinite(evaluatedAtMs)) {
981
+ return false;
982
+ }
983
+ const maxAgeMs = Math.max(0, Math.floor(maxEvidenceAgeDays)) * 24 * 60 * 60 * 1000;
984
+ const evidenceAgeMs = evaluatedAtMs - collectedAtMs;
985
+ return evidenceAgeMs >= 0 && evidenceAgeMs <= maxAgeMs;
986
+ }
987
+ function isHostedContainerImageDigest(digest) {
988
+ return /^sha256:[a-f0-9]{64}$/i.test(digest ?? "");
989
+ }
990
+ function findHostedReleaseNoteForbiddenClaims(releaseNotes) {
991
+ const claims = [];
992
+ const sentences = releaseNotes.split(/[.!?]\s+/).filter((sentence) => sentence.trim());
993
+ if (sentences.some((sentence) => hasPositiveHostedReleaseClaim(sentence, /\b(?:pentest|penetration test)\b/i))) {
994
+ claims.push("pentest_claim");
995
+ }
996
+ if (sentences.some((sentence) => hasPositiveHostedReleaseClaim(sentence, /\b(?:certification|certified)\b/i))) {
997
+ claims.push("certification_claim");
998
+ }
999
+ if (sentences.some((sentence) => hasPositiveHostedReleaseClaim(sentence, /\bfull(?:\s+security)?\s+audit\b/i))) {
1000
+ claims.push("full_audit_claim");
1001
+ }
1002
+ return claims;
1003
+ }
1004
+ function hasPositiveHostedReleaseClaim(sentence, termPattern) {
1005
+ const termRegex = new RegExp(termPattern.source, termPattern.flags.replace("g", ""));
1006
+ const termMatch = termRegex.exec(sentence);
1007
+ if (!termMatch || termMatch.index === undefined) {
1008
+ return false;
1009
+ }
1010
+ const prefix = sentence.slice(Math.max(0, termMatch.index - 120), termMatch.index);
1011
+ const lastClaimVerbIndex = lastRegexMatchIndex(prefix, /\b(?:is|are|as|provides?|delivers?|offers?|certifies?)\b/gi);
1012
+ if (lastClaimVerbIndex === -1) {
1013
+ return false;
1014
+ }
1015
+ const lastNegationIndex = lastRegexMatchIndex(prefix, /\b(?:not|never|does not|do not|is not|are not)\b/gi);
1016
+ return lastNegationIndex < lastClaimVerbIndex;
1017
+ }
1018
+ function lastRegexMatchIndex(value, pattern) {
1019
+ let lastIndex = -1;
1020
+ for (const match of value.matchAll(pattern)) {
1021
+ if (match.index !== undefined) {
1022
+ lastIndex = match.index;
1023
+ }
1024
+ }
1025
+ return lastIndex;
1026
+ }
717
1027
  function isTerminalQueueStatus(status) {
718
1028
  return status === "completed" || status === "failed" || status === "cancelled";
719
1029
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  `ai-saas-guard` ships as a composite GitHub Action for pull request and code scanning workflows.
4
4
 
5
- Use `zr9959/ai-saas-guard@v0` for the latest compatible pre-1.0 Action. Use a specific tag such as `v0.14.0` or a reviewed commit SHA when reproducibility is more important than automatic minor updates.
5
+ Use `zr9959/ai-saas-guard@v0` for the latest compatible pre-1.0 Action. Use a specific tag such as `v0.16.0` or a reviewed commit SHA when reproducibility is more important than automatic minor updates.
6
6
 
7
7
  ## PR Summary
8
8
 
@@ -36,6 +36,31 @@ Every hosted release must record:
36
36
  9. Monitoring and alerting checks cover ingress, queue depth, worker failures, check run failures, and cleanup failures.
37
37
  10. Manual rollback is tested against the release candidate.
38
38
 
39
+ ## Current Source Candidate Evidence Notes
40
+
41
+ The current public package release is still a local CLI and pure hosted-contract release. No hosted production environment is exposed by this release.
42
+
43
+ The pure evaluator `evaluateHostedOperationalReleaseGate` and the exported `HOSTED_OPERATIONAL_RELEASE_GATE_REQUIREMENTS` list make the gate machine-checkable for the next hosted service stage. The evaluator blocks hosted exposure unless every P0 item has fresh evidence, a `sha256:<digest>` container image digest is recorded, and release notes avoid positive pentest, certification, and full-audit claims. Explicit wording such as "not a pentest, certification, or full security audit" remains allowed.
44
+
45
+ Source-level evidence notes for this release candidate:
46
+
47
+ | Gate ID | Requirement | Evidence link or note | Current source status |
48
+ | --- | --- | --- | --- |
49
+ | `clean_ci` | Clean install, tests, build, CLI help, JSON/SARIF scan, PR-risk, npm audit, pack dry-run | Local release gate plus GitHub Actions CI run from the release commit | Passed for source package |
50
+ | `hosted_contract_tests` | Hosted contract tests for webhook, scope, queue, worker, check summaries, cleanup, retention, and release gate evaluation | `tests/hosted-contracts.test.mjs` | Passed for pure contracts |
51
+ | `webhook_replay` | Valid events queue work; invalid, missing, malformed, replayed, removed, and non-installed events queue nothing | Pure replay coverage in hosted webhook intake tests | Passed for pure contracts; must replay against deployed ingress before exposure |
52
+ | `workflow_static_checks` | GitHub Actions static analysis | `actionlint` and `uvx zizmor --offline .github/workflows` | Passed for repository workflows |
53
+ | `dependency_scan` | Dependency scan has no unresolved high or critical production findings | `npm audit --audit-level=high --registry=https://registry.npmjs.org` | Passed for source package |
54
+ | `container_scan` | Container image scan has no unresolved high or critical runtime-layer findings | No hosted container image exists in the public package release | Not applicable to current non-hosted release; required before hosted exposure |
55
+ | `queue_worker_cleanup` | Queue dedupe, running cancellation, terminal cleanup, worker checkout deletion, and no long-running processes | Pure queue, worker, checkout, and retention cleanup planner tests | Passed for pure contracts; must verify against deployed queue and worker before exposure |
56
+ | `privacy_retention` | No raw source, raw diffs, secrets, customer payloads, private URLs, or full file contents; retention and uninstall cleanup are proven | Compact report, Check Run publication, retention/deletion cleanup, and docs tests | Passed for pure contracts; log sampling still required before exposure |
57
+ | `monitoring_alerting` | Ingress, queue depth, worker failures, Check Run failures, cleanup failures, retention failures, and credential rotation alerts | Required alert list remains in this document | Documented; must attach provider evidence before exposure |
58
+ | `manual_rollback` | Worker pause, previous artifact redeploy, queue resume, controlled ingress failure, and affected Check Run identification | Manual rollback procedure remains in this document | Documented; must execute against deployed artifact before exposure |
59
+ | `incident_response` | Owner, backup, credential rotation, queue pause, customer communication, status path, and privacy-safe evidence collection | Incident response checklist remains in this document | Documented; must name live owners before exposure |
60
+ | `release_cleanup` | Temporary files, package tarballs, scratch SARIF/JSON, test queues/stores, and long-running processes are removed | Local cleanup checks after each release task | Passed for local release run |
61
+
62
+ For the current non-hosted package, these notes are enough to keep the repository implementation-ready while still blocking real hosted exposure until the deployment-specific evidence rows are completed with live provider links or notes.
63
+
39
64
  ## CI Checks
40
65
 
41
66
  Hosted release CI must include the existing public package gate:
@@ -195,6 +195,60 @@ Cleanup failure behavior:
195
195
  - require manual cleanup review
196
196
  - preserve an audit record without exposing checkout contents
197
197
 
198
+ ## Retention And Deletion Cleanup Planner
199
+
200
+ The retention and deletion cleanup planner composes the hosted cleanup contracts into one implementation-ready plan for repository removal, full app uninstall, repeated cleanup requests, and compact report retention expiry. It is a pure planner only: it does not connect to storage, mutate queues, delete files, call GitHub, retry work, or fetch repository content.
201
+
202
+ Default behavior:
203
+
204
+ - support repository-scoped cleanup when a repository is removed from an installation
205
+ - support installation-scoped cleanup when the GitHub App is uninstalled
206
+ - keep repeated cleanup idempotent with a stable cleanup key
207
+ - delete only compact reports that match the affected installation and repository scope
208
+ - cancel queued jobs and request running cancellation only for matching scope
209
+ - preserve completed, failed, and cancelled terminal jobs as audit-safe terminal state
210
+ - delete matching worker checkouts while keeping unmatched installation or repository checkouts untouched
211
+ - expire only compact reports past their explicit `expiresAt` timestamp or derived retention window
212
+ - cap audit record retention at the hosted default unless a stricter shorter policy is requested
213
+ - never fetch source, requeue scans, or delete GitHub-owned Check Runs during cleanup
214
+
215
+ Minimal audit record:
216
+
217
+ - cleanup request ID
218
+ - trigger
219
+ - status
220
+ - installation ID
221
+ - repository ID when repository-scoped
222
+ - requested time
223
+
224
+ Privacy boundaries:
225
+
226
+ - do not return raw source, raw diffs, secret values, customer payloads, private URLs, worker checkout paths, or low-level cleanup errors
227
+ - keep user-facing deletion wording precise: hosted app-side compact reports and queued work are removed; GitHub-owned check runs remain in GitHub according to repository settings
228
+
229
+ The exported helper is `planHostedRetentionAndDeletionCleanup`. It is intended to be the storage-, queue-, and worker-provider-independent contract for the first real hosted cleanup implementation.
230
+
231
+ ## Operational Release Gate Evaluator
232
+
233
+ The operational release gate evaluator turns the hosted release gate into a pure, machine-checkable decision. It is a pure evaluator only: it does not deploy containers, inspect cloud accounts, run scanners, call GitHub, publish npm packages, or create release notes.
234
+
235
+ Default behavior:
236
+
237
+ - require fresh P0 evidence for clean CI, hosted contract tests, webhook replay, workflow static checks, dependency scan, container scan, queue and worker cleanup, privacy and retention, monitoring and alerting, manual rollback, incident response, and release cleanup
238
+ - require each evidence item to include either an evidence URL or a concrete note
239
+ - treat missing, failed, stale, or exception-marked P0 evidence as release blockers
240
+ - require a `sha256:<digest>` container image digest before hosted exposure
241
+ - reject release notes that make positive pentest, certification, or full-audit claims
242
+ - preserve the local-first boundary: the local CLI remains usable without the hosted service and without an account
243
+
244
+ Privacy boundaries:
245
+
246
+ - return only requirement IDs, status IDs, version metadata, deployment target, and high-level decision fields
247
+ - do not return raw source, raw diffs, secrets, customer payloads, or private URLs
248
+ - preserve `modelTraining: disabled`
249
+
250
+ The exported helper is `evaluateHostedOperationalReleaseGate`. The exported requirement list is `HOSTED_OPERATIONAL_RELEASE_GATE_REQUIREMENTS`.
251
+
198
252
  ## Hosted Compact Report Fixture
199
253
 
200
254
  A public hosted compact report fixture is available at [examples/hosted-compact-report.json](../examples/hosted-compact-report.json). It is intentionally synthetic and shows the report shape future hosted components can pass between the worker, check-run summary renderer, and retention cleanup logic.
@@ -256,6 +310,15 @@ Automated tests must cover:
256
310
  - worker checkout cleanup planner covers success, failure, timeout, cancellation, and cleanup_failure terminal states
257
311
  - worker checkout cleanup planner returns safe metadata only and never returns checkout paths
258
312
  - cleanup_failure requires manual cleanup review without exposing low-level cleanup errors
313
+ - retention and deletion cleanup planner deletes only matching repository-scoped compact reports, queued jobs, and worker checkouts
314
+ - retention and deletion cleanup planner handles full app uninstall without touching other installations
315
+ - retention and deletion cleanup planner keeps repeated cleanup idempotent and preserves terminal jobs
316
+ - retention cleanup expires only compact reports past their retention window
317
+ - retention and deletion cleanup planner preserves only minimal audit records
318
+ - operational release gate evaluator passes only when all required P0 evidence is present and fresh
319
+ - operational release gate evaluator blocks hosted exposure when evidence is missing, failed, stale, or exception-marked
320
+ - operational release gate evaluator blocks hosted exposure when the container digest is missing
321
+ - operational release gate evaluator rejects positive pentest, certification, and full-audit claims while allowing explicit "not a pentest" wording
259
322
  - hosted compact report fixture remains schema-compatible and public-safe
260
323
  - summary counts with an explicit `total` are not double-counted by check-run summaries
261
324
 
@@ -151,6 +151,8 @@ Automated tests must cover:
151
151
 
152
152
  The current pure contract helpers live in `src/hosted/contracts.ts` and are tested by `tests/hosted-contracts.test.mjs`.
153
153
 
154
+ The implementation-ready aggregate cleanup helper is `planHostedRetentionAndDeletionCleanup`. It composes compact report deletion, queue cancellation, running-job cancellation requests, worker checkout deletion, retention expiry, user-facing deletion wording, and minimal audit records into one provider-independent plan.
155
+
154
156
  ## Operational Gate
155
157
 
156
158
  The hosted operational release gate must include cleanup evidence before deployment:
@@ -5,10 +5,10 @@
5
5
  ## Current State
6
6
 
7
7
  - Package name: `ai-saas-guard`
8
- - Current version: `0.14.0`
8
+ - Current version: `0.16.0`
9
9
  - npm registry state: published at <https://www.npmjs.com/package/ai-saas-guard>
10
10
  - First npm-published version: `0.1.1`
11
- - GitHub Release: `v0.14.0`
11
+ - GitHub Release: `v0.16.0`
12
12
  - Publish workflow: `.github/workflows/npm-publish.yml`
13
13
  - Trusted Publisher: GitHub Actions, `zr9959/ai-saas-guard`, workflow `npm-publish.yml`, allowed action `npm publish`
14
14
  - Long-lived npm publish token: not required
@@ -17,7 +17,7 @@
17
17
 
18
18
  Use GitHub Actions with npm Trusted Publisher/OIDC:
19
19
 
20
- 1. Create and review a release tag such as `v0.14.0`.
20
+ 1. Create and review a release tag such as `v0.16.0`.
21
21
  2. Publish from the GitHub Release or run the `Publish npm` workflow manually with `ref` set to that tag.
22
22
  3. Keep `permissions.id-token: write` in the workflow so npm can exchange the GitHub Actions OIDC identity for a short-lived publish credential.
23
23
  4. Run `npm publish --access public` from the workflow. Trusted publishing automatically generates provenance for this public package from this public repository.
@@ -57,9 +57,9 @@ Implemented surfaces:
57
57
  - hosted operational release gate document requiring hosted CI, webhook replay, dependency and container scanning, privacy and retention verification, worker cleanup, monitoring and alerting, manual rollback, and incident response evidence before exposure
58
58
  - hosted uninstall and data deletion document defining repository removal, full app uninstall, compact report deletion, queue cancellation, audit record retention, repeated cleanup idempotency, and user-facing deletion wording
59
59
  - hosted pricing and packaging document defining open-source CLI boundaries, free/public repo hosted behavior, private repo hosted behavior, PR comments, saved reports, team policy, optional Launch Review, and no pentest/certification/full-audit claims
60
- - hosted pre-implementation contracts document, hosted compact report fixture, and pure helpers for pull request webhook intake planning, durable scan queue upsert planning, worker read-only scan planning, Check Run publication planning, queue-safe pull request event parsing from trusted GitHub event fields, bounded check-run summary rendering, idempotent queue cleanup planning, and worker checkout cleanup planning
60
+ - hosted pre-implementation contracts document, hosted compact report fixture, and pure helpers for pull request webhook intake planning, durable scan queue upsert planning, worker read-only scan planning, Check Run publication planning, queue-safe pull request event parsing from trusted GitHub event fields, bounded check-run summary rendering, idempotent queue cleanup planning, worker checkout cleanup planning, retention/deletion cleanup planning, and operational release gate evaluation
61
61
  - implementation-ready hosted GitHub App permission contract for required permissions, optional PR comment permissions, selected repository installation, and out-of-scope broad permissions
62
- - pure hosted GitHub App contract helpers and tests for webhook intake order, webhook verification, installation token scoping, durable scan queue idempotency, compact reports, and retention limits
62
+ - pure hosted GitHub App contract helpers and tests for webhook intake order, webhook verification, installation token scoping, durable scan queue idempotency, compact reports, retention limits, uninstall cleanup, repeated cleanup idempotency, scoped deletion planning, and operational release gate blocking
63
63
  - GitHub issue templates for bug reports, false positives, false negatives, rule requests, and public-safe security reports
64
64
  - CODEOWNERS for source, tests, docs, workflows, Action, and package metadata
65
65
  - JSON output
@@ -116,19 +116,21 @@ Current issue set:
116
116
  - Closed hosted MVP issue: #24 webhook intake.
117
117
  - Closed hosted MVP issue: #25 idempotent queue contract.
118
118
  - Closed hosted MVP issue: #26 read-only worker checkout.
119
- - Open hosted MVP roadmap issues: #27 Check summaries, #28 retention/uninstall cleanup, and #29 hosted operational release gate.
119
+ - Closed hosted MVP issue: #27 Check summaries.
120
+ - Closed hosted MVP issue: #28 retention/uninstall cleanup.
121
+ - Closed hosted MVP issue: #29 hosted operational release gate.
120
122
 
121
123
  CI:
122
124
 
123
125
  - Workflow: `.github/workflows/ci.yml`
124
126
  - Runs on pull requests and pushes to `main`
125
127
  - Uses `permissions: contents: read`
126
- - Latest verified run for the hosted read-only worker plan release succeeded
128
+ - Latest verified run for the hosted Check Run publication release succeeded
127
129
 
128
130
  Publishing:
129
131
 
130
132
  - npm package: `ai-saas-guard`
131
- - Current release line: `v0.14.0`
133
+ - Current release line: `v0.16.0`
132
134
  - Publish workflow: `.github/workflows/npm-publish.yml`
133
135
  - Trusted Publisher: GitHub Actions for `zr9959/ai-saas-guard`, workflow `npm-publish.yml`
134
136
  - Long-lived npm publish tokens should not be required.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-saas-guard",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Repo-local launch-readiness scanner for AI-built SaaS apps.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/zr9959/ai-saas-guard#readme",