@absolutejs/vulnerabilities-postgres 0.3.0 → 0.5.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
@@ -14,8 +14,8 @@ const osvSnapshots = persistence.snapshots();
14
14
  ```
15
15
 
16
16
  The package stores complete provider snapshots and records, appendable sync
17
- history, tenant-scoped managed findings, correlation observations, risk
18
- assessments, and expiring distributed refresh leases. Snapshot replacement is
17
+ history, tenant-scoped managed findings, correlation observations, VEX decisions
18
+ and applications, remediation plans and evidence, risk assessments, and expiring distributed refresh leases. Snapshot replacement is
19
19
  one Postgres statement, so readers never see a half-replaced record set. Schema
20
20
  creation is lazy and idempotent, or can be disabled when application migrations
21
21
  own the tables.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type FeedSnapshotStore, type FeedSyncRunStore, type ManagedFindingStore, type VulnerabilityObservationStore, type VulnerabilityRiskAssessmentStore } from "@absolutejs/vulnerabilities";
1
+ import { type FeedSnapshotStore, type FeedSyncRunStore, type ManagedFindingStore, type RemediationExecutionStore, type RemediationPlanStore, type RemediationVerificationStore, type VulnerabilityObservationStore, type VulnerabilityRiskAssessmentStore, type VexDecisionStore, type VexFindingApplicationStore } from "@absolutejs/vulnerabilities";
2
2
  export type PostgresTag = {
3
3
  (strings: TemplateStringsArray, ...values: never[]): PromiseLike<unknown[]>;
4
4
  unsafe: (sql: string, ...args: never[]) => PromiseLike<unknown[]>;
@@ -18,9 +18,14 @@ export type PostgresVulnerabilityStore = {
18
18
  findings: ManagedFindingStore;
19
19
  leases: FeedLeaseStore;
20
20
  observations: VulnerabilityObservationStore;
21
+ remediationExecutions: RemediationExecutionStore;
22
+ remediationPlans: RemediationPlanStore;
23
+ remediationVerifications: RemediationVerificationStore;
21
24
  riskAssessments: VulnerabilityRiskAssessmentStore;
22
25
  snapshots: <T>() => FeedSnapshotStore<T>;
23
26
  syncRuns: FeedSyncRunStore;
27
+ vexApplications: VexFindingApplicationStore;
28
+ vexDecisions: VexDecisionStore;
24
29
  };
25
30
  export type CreatePostgresVulnerabilityStoreOptions = {
26
31
  ensureSchema?: boolean;
package/dist/index.js CHANGED
@@ -2,8 +2,13 @@
2
2
  // src/index.ts
3
3
  import {
4
4
  ManagedVulnerabilityFindingSchema,
5
+ RemediationExecutionSchema,
6
+ RemediationPlanSchema,
7
+ RemediationVerificationSchema,
5
8
  VulnerabilityObservationSchema,
6
- VulnerabilityRiskAssessmentSchema
9
+ VulnerabilityRiskAssessmentSchema,
10
+ VexDecisionSchema,
11
+ VexFindingApplicationSchema
7
12
  } from "@absolutejs/vulnerabilities";
8
13
  import { Value } from "@sinclair/typebox/value";
9
14
  var IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
@@ -59,10 +64,15 @@ var prefixTables = (prefix) => ({
59
64
  findings: `${prefix}_findings`,
60
65
  leases: `${prefix}_feed_leases`,
61
66
  observations: `${prefix}_observations`,
67
+ remediationExecutions: `${prefix}_remediation_executions`,
68
+ remediationPlans: `${prefix}_remediation_plans`,
69
+ remediationVerifications: `${prefix}_remediation_verifications`,
62
70
  riskAssessments: `${prefix}_risk_assessments`,
63
71
  records: `${prefix}_feed_records`,
64
72
  runs: `${prefix}_feed_sync_runs`,
65
- snapshots: `${prefix}_feed_snapshots`
73
+ snapshots: `${prefix}_feed_snapshots`,
74
+ vexApplications: `${prefix}_vex_applications`,
75
+ vexDecisions: `${prefix}_vex_decisions`
66
76
  });
67
77
  var vulnerabilityPostgresSchemaSql = (tablePrefix = "vulnerability") => {
68
78
  if (!IDENTIFIER.test(tablePrefix))
@@ -149,6 +159,71 @@ var vulnerabilityPostgresSchemaSql = (tablePrefix = "vulnerability") => {
149
159
  CREATE INDEX IF NOT EXISTS ${table.riskAssessments}_tenant_due_idx
150
160
  ON ${table.riskAssessments} (tenant_id, remediate_by)
151
161
  WHERE remediate_by IS NOT NULL;
162
+ CREATE TABLE IF NOT EXISTS ${table.remediationPlans} (
163
+ tenant_id text NOT NULL,
164
+ plan_id text NOT NULL,
165
+ status text NOT NULL,
166
+ created_at timestamptz NOT NULL,
167
+ value jsonb NOT NULL,
168
+ updated_at timestamptz NOT NULL DEFAULT now(),
169
+ PRIMARY KEY (tenant_id, plan_id)
170
+ );
171
+ CREATE INDEX IF NOT EXISTS ${table.remediationPlans}_tenant_status_idx
172
+ ON ${table.remediationPlans} (tenant_id, status, created_at DESC);
173
+ CREATE TABLE IF NOT EXISTS ${table.remediationExecutions} (
174
+ tenant_id text NOT NULL,
175
+ execution_id text NOT NULL,
176
+ plan_id text NOT NULL,
177
+ status text NOT NULL,
178
+ started_at timestamptz NOT NULL,
179
+ completed_at timestamptz,
180
+ value jsonb NOT NULL,
181
+ updated_at timestamptz NOT NULL DEFAULT now(),
182
+ PRIMARY KEY (tenant_id, execution_id)
183
+ );
184
+ CREATE INDEX IF NOT EXISTS ${table.remediationExecutions}_tenant_plan_idx
185
+ ON ${table.remediationExecutions} (tenant_id, plan_id, started_at DESC);
186
+ CREATE TABLE IF NOT EXISTS ${table.remediationVerifications} (
187
+ tenant_id text NOT NULL,
188
+ verification_id text NOT NULL,
189
+ execution_id text NOT NULL,
190
+ plan_id text NOT NULL,
191
+ status text NOT NULL,
192
+ observed_at timestamptz NOT NULL,
193
+ value jsonb NOT NULL,
194
+ updated_at timestamptz NOT NULL DEFAULT now(),
195
+ PRIMARY KEY (tenant_id, verification_id)
196
+ );
197
+ CREATE INDEX IF NOT EXISTS ${table.remediationVerifications}_tenant_execution_idx
198
+ ON ${table.remediationVerifications} (tenant_id, execution_id, observed_at DESC);
199
+ CREATE TABLE IF NOT EXISTS ${table.vexDecisions} (
200
+ tenant_id text NOT NULL,
201
+ decision_id text NOT NULL,
202
+ product_id text NOT NULL,
203
+ vulnerability_id text NOT NULL,
204
+ status text NOT NULL,
205
+ created_at timestamptz NOT NULL,
206
+ expires_at timestamptz,
207
+ value jsonb NOT NULL,
208
+ updated_at timestamptz NOT NULL DEFAULT now(),
209
+ PRIMARY KEY (tenant_id, decision_id)
210
+ );
211
+ CREATE INDEX IF NOT EXISTS ${table.vexDecisions}_tenant_product_idx
212
+ ON ${table.vexDecisions} (tenant_id, product_id, created_at DESC);
213
+ CREATE INDEX IF NOT EXISTS ${table.vexDecisions}_tenant_vulnerability_idx
214
+ ON ${table.vexDecisions} (tenant_id, vulnerability_id, created_at DESC);
215
+ CREATE TABLE IF NOT EXISTS ${table.vexApplications} (
216
+ tenant_id text NOT NULL,
217
+ finding_id text NOT NULL,
218
+ decision_id text NOT NULL,
219
+ applied_at timestamptz NOT NULL,
220
+ ended_at timestamptz,
221
+ value jsonb NOT NULL,
222
+ updated_at timestamptz NOT NULL DEFAULT now(),
223
+ PRIMARY KEY (tenant_id, finding_id)
224
+ );
225
+ CREATE INDEX IF NOT EXISTS ${table.vexApplications}_tenant_decision_idx
226
+ ON ${table.vexApplications} (tenant_id, decision_id, applied_at DESC);
152
227
  CREATE TABLE IF NOT EXISTS ${table.leases} (
153
228
  feed_id text PRIMARY KEY,
154
229
  owner_id text NOT NULL,
@@ -552,6 +627,293 @@ var createPostgresVulnerabilityStore = (options) => {
552
627
  save: async (tenantId, assessment) => saveRiskAssessments(tenantId, [assessment]),
553
628
  saveMany: saveRiskAssessments
554
629
  };
630
+ const parseRemediationPlan = (value) => {
631
+ const plan = json(value, "Remediation plan");
632
+ if (!Value.Check(RemediationPlanSchema, plan))
633
+ throw new Error("Stored remediation plan is invalid");
634
+ return plan;
635
+ };
636
+ const remediationPlans = {
637
+ get: async (tenantId, planId) => {
638
+ await ensureSchema();
639
+ const rows = await sql`
640
+ SELECT value FROM ${sql.unsafe(table.remediationPlans)}
641
+ WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
642
+ AND plan_id = ${requiredText(planId, "Remediation plan id")}
643
+ `;
644
+ return rows[0] ? parseRemediationPlan(rows[0].value) : null;
645
+ },
646
+ list: async (filter) => {
647
+ await ensureSchema();
648
+ const tenantId = requiredText(filter.tenantId, "Tenant id");
649
+ const status = filter.status ?? null;
650
+ const rows = await sql`
651
+ SELECT value FROM ${sql.unsafe(table.remediationPlans)}
652
+ WHERE tenant_id = ${tenantId}
653
+ AND (${status}::text IS NULL OR status = ${status})
654
+ ORDER BY created_at DESC, plan_id
655
+ LIMIT ${limit(filter.limit)}
656
+ `;
657
+ return rows.map(({ value }) => parseRemediationPlan(value));
658
+ },
659
+ save: async (tenantId, plan) => {
660
+ await ensureSchema();
661
+ const planId = plan.id;
662
+ if (!Value.Check(RemediationPlanSchema, plan))
663
+ throw new Error(`Remediation plan ${planId} is invalid`);
664
+ await sql`
665
+ INSERT INTO ${sql.unsafe(table.remediationPlans)} (
666
+ tenant_id, plan_id, status, created_at, value, updated_at
667
+ ) VALUES (
668
+ ${requiredText(tenantId, "Tenant id")}, ${plan.id}, ${plan.status},
669
+ ${plan.createdAt}::timestamptz, ${JSON.stringify(plan)}::jsonb, now()
670
+ )
671
+ ON CONFLICT (tenant_id, plan_id) DO UPDATE SET
672
+ status = EXCLUDED.status,
673
+ created_at = EXCLUDED.created_at,
674
+ value = EXCLUDED.value,
675
+ updated_at = now()
676
+ `;
677
+ }
678
+ };
679
+ const parseRemediationExecution = (value) => {
680
+ const execution = json(value, "Remediation execution");
681
+ if (!Value.Check(RemediationExecutionSchema, execution))
682
+ throw new Error("Stored remediation execution is invalid");
683
+ return execution;
684
+ };
685
+ const remediationExecutions = {
686
+ get: async (tenantId, executionId) => {
687
+ await ensureSchema();
688
+ const rows = await sql`
689
+ SELECT value FROM ${sql.unsafe(table.remediationExecutions)}
690
+ WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
691
+ AND execution_id = ${requiredText(executionId, "Remediation execution id")}
692
+ `;
693
+ return rows[0] ? parseRemediationExecution(rows[0].value) : null;
694
+ },
695
+ list: async (tenantId, planId) => {
696
+ await ensureSchema();
697
+ const rows = await sql`
698
+ SELECT value FROM ${sql.unsafe(table.remediationExecutions)}
699
+ WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
700
+ AND plan_id = ${requiredText(planId, "Remediation plan id")}
701
+ ORDER BY started_at DESC, execution_id
702
+ LIMIT 1000
703
+ `;
704
+ return rows.map(({ value }) => parseRemediationExecution(value));
705
+ },
706
+ save: async (tenantId, execution) => {
707
+ await ensureSchema();
708
+ const executionId = execution.id;
709
+ if (!Value.Check(RemediationExecutionSchema, execution))
710
+ throw new Error(`Remediation execution ${executionId} is invalid`);
711
+ await sql`
712
+ INSERT INTO ${sql.unsafe(table.remediationExecutions)} (
713
+ tenant_id, execution_id, plan_id, status, started_at, completed_at,
714
+ value, updated_at
715
+ ) VALUES (
716
+ ${requiredText(tenantId, "Tenant id")}, ${execution.id},
717
+ ${execution.planId}, ${execution.status},
718
+ ${execution.startedAt}::timestamptz,
719
+ ${execution.completedAt}::timestamptz,
720
+ ${JSON.stringify(execution)}::jsonb, now()
721
+ )
722
+ ON CONFLICT (tenant_id, execution_id) DO UPDATE SET
723
+ plan_id = EXCLUDED.plan_id,
724
+ status = EXCLUDED.status,
725
+ started_at = EXCLUDED.started_at,
726
+ completed_at = EXCLUDED.completed_at,
727
+ value = EXCLUDED.value,
728
+ updated_at = now()
729
+ `;
730
+ }
731
+ };
732
+ const parseRemediationVerification = (value) => {
733
+ const verification = json(value, "Remediation verification");
734
+ if (!Value.Check(RemediationVerificationSchema, verification))
735
+ throw new Error("Stored remediation verification is invalid");
736
+ return verification;
737
+ };
738
+ const remediationVerifications = {
739
+ get: async (tenantId, verificationId) => {
740
+ await ensureSchema();
741
+ const rows = await sql`
742
+ SELECT value FROM ${sql.unsafe(table.remediationVerifications)}
743
+ WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
744
+ AND verification_id = ${requiredText(verificationId, "Remediation verification id")}
745
+ `;
746
+ return rows[0] ? parseRemediationVerification(rows[0].value) : null;
747
+ },
748
+ list: async (tenantId, executionId) => {
749
+ await ensureSchema();
750
+ const rows = await sql`
751
+ SELECT value FROM ${sql.unsafe(table.remediationVerifications)}
752
+ WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
753
+ AND execution_id = ${requiredText(executionId, "Remediation execution id")}
754
+ ORDER BY observed_at DESC, verification_id
755
+ LIMIT 1000
756
+ `;
757
+ return rows.map(({ value }) => parseRemediationVerification(value));
758
+ },
759
+ save: async (tenantId, verification) => {
760
+ await ensureSchema();
761
+ const verificationId = verification.id;
762
+ if (!Value.Check(RemediationVerificationSchema, verification))
763
+ throw new Error(`Remediation verification ${verificationId} is invalid`);
764
+ await sql`
765
+ INSERT INTO ${sql.unsafe(table.remediationVerifications)} (
766
+ tenant_id, verification_id, execution_id, plan_id, status,
767
+ observed_at, value, updated_at
768
+ ) VALUES (
769
+ ${requiredText(tenantId, "Tenant id")}, ${verification.id},
770
+ ${verification.executionId}, ${verification.planId},
771
+ ${verification.status}, ${verification.observedAt}::timestamptz,
772
+ ${JSON.stringify(verification)}::jsonb, now()
773
+ )
774
+ ON CONFLICT (tenant_id, verification_id) DO UPDATE SET
775
+ execution_id = EXCLUDED.execution_id,
776
+ plan_id = EXCLUDED.plan_id,
777
+ status = EXCLUDED.status,
778
+ observed_at = EXCLUDED.observed_at,
779
+ value = EXCLUDED.value,
780
+ updated_at = now()
781
+ `;
782
+ }
783
+ };
784
+ const parseVexDecision = (value) => {
785
+ const decision = json(value, "VEX decision");
786
+ if (!Value.Check(VexDecisionSchema, decision))
787
+ throw new Error("Stored VEX decision is invalid");
788
+ return decision;
789
+ };
790
+ const saveVexDecisions = async (tenantId, decisions) => {
791
+ await ensureSchema();
792
+ const normalizedTenantId = requiredText(tenantId, "Tenant id");
793
+ if (decisions.length === 0)
794
+ return;
795
+ for (const decision of decisions) {
796
+ const decisionId = decision.id;
797
+ if (!Value.Check(VexDecisionSchema, decision))
798
+ throw new Error(`VEX decision ${decisionId} is invalid`);
799
+ }
800
+ const payload = JSON.stringify(decisions.map((decision) => ({
801
+ created_at: decision.createdAt,
802
+ decision_id: decision.id,
803
+ expires_at: decision.expiresAt,
804
+ product_id: decision.productId,
805
+ status: decision.status,
806
+ value: decision,
807
+ vulnerability_id: decision.vulnerabilityId
808
+ })));
809
+ await sql`
810
+ INSERT INTO ${sql.unsafe(table.vexDecisions)} (
811
+ tenant_id, decision_id, product_id, vulnerability_id, status,
812
+ created_at, expires_at, value, updated_at
813
+ )
814
+ SELECT
815
+ ${normalizedTenantId}, item.decision_id, item.product_id,
816
+ item.vulnerability_id, item.status, item.created_at::timestamptz,
817
+ item.expires_at::timestamptz, item.value, now()
818
+ FROM jsonb_to_recordset(${payload}::jsonb) AS item(
819
+ decision_id text, product_id text, vulnerability_id text, status text,
820
+ created_at text, expires_at text, value jsonb
821
+ )
822
+ ON CONFLICT (tenant_id, decision_id) DO UPDATE SET
823
+ product_id = EXCLUDED.product_id,
824
+ vulnerability_id = EXCLUDED.vulnerability_id,
825
+ status = EXCLUDED.status,
826
+ created_at = EXCLUDED.created_at,
827
+ expires_at = EXCLUDED.expires_at,
828
+ value = EXCLUDED.value,
829
+ updated_at = now()
830
+ `;
831
+ };
832
+ const vexDecisions = {
833
+ get: async (tenantId, decisionId) => {
834
+ await ensureSchema();
835
+ const rows = await sql`
836
+ SELECT value FROM ${sql.unsafe(table.vexDecisions)}
837
+ WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
838
+ AND decision_id = ${requiredText(decisionId, "Decision id")}
839
+ `;
840
+ return rows[0] ? parseVexDecision(rows[0].value) : null;
841
+ },
842
+ list: async (filter) => {
843
+ await ensureSchema();
844
+ const tenantId = requiredText(filter.tenantId, "Tenant id");
845
+ const productId = filter.productId?.trim() || null;
846
+ const vulnerabilityId = filter.vulnerabilityId?.trim() || null;
847
+ const rows = await sql`
848
+ SELECT value FROM ${sql.unsafe(table.vexDecisions)}
849
+ WHERE tenant_id = ${tenantId}
850
+ AND (${productId}::text IS NULL OR product_id = ${productId})
851
+ AND (${vulnerabilityId}::text IS NULL OR vulnerability_id = ${vulnerabilityId})
852
+ ORDER BY created_at DESC, decision_id
853
+ LIMIT ${limit(filter.limit)}
854
+ `;
855
+ return rows.map(({ value }) => parseVexDecision(value));
856
+ },
857
+ save: async (tenantId, decision) => saveVexDecisions(tenantId, [decision]),
858
+ saveMany: saveVexDecisions
859
+ };
860
+ const parseVexApplication = (value) => {
861
+ const application = json(value, "VEX application");
862
+ if (!Value.Check(VexFindingApplicationSchema, application))
863
+ throw new Error("Stored VEX application is invalid");
864
+ return application;
865
+ };
866
+ const saveVexApplications = async (applications) => {
867
+ await ensureSchema();
868
+ if (applications.length === 0)
869
+ return;
870
+ for (const application of applications) {
871
+ const findingId = application.findingId;
872
+ if (!Value.Check(VexFindingApplicationSchema, application))
873
+ throw new Error(`VEX application ${findingId} is invalid`);
874
+ }
875
+ const payload = JSON.stringify(applications.map((application) => ({
876
+ applied_at: application.appliedAt,
877
+ decision_id: application.decisionId,
878
+ ended_at: application.endedAt,
879
+ finding_id: application.findingId,
880
+ tenant_id: application.tenantId,
881
+ value: application
882
+ })));
883
+ await sql`
884
+ INSERT INTO ${sql.unsafe(table.vexApplications)} (
885
+ tenant_id, finding_id, decision_id, applied_at, ended_at, value,
886
+ updated_at
887
+ )
888
+ SELECT
889
+ item.tenant_id, item.finding_id, item.decision_id,
890
+ item.applied_at::timestamptz, item.ended_at::timestamptz,
891
+ item.value, now()
892
+ FROM jsonb_to_recordset(${payload}::jsonb) AS item(
893
+ tenant_id text, finding_id text, decision_id text, applied_at text,
894
+ ended_at text, value jsonb
895
+ )
896
+ ON CONFLICT (tenant_id, finding_id) DO UPDATE SET
897
+ decision_id = EXCLUDED.decision_id,
898
+ applied_at = EXCLUDED.applied_at,
899
+ ended_at = EXCLUDED.ended_at,
900
+ value = EXCLUDED.value,
901
+ updated_at = now()
902
+ `;
903
+ };
904
+ const vexApplications = {
905
+ get: async (tenantId, findingId) => {
906
+ await ensureSchema();
907
+ const rows = await sql`
908
+ SELECT value FROM ${sql.unsafe(table.vexApplications)}
909
+ WHERE tenant_id = ${requiredText(tenantId, "Tenant id")}
910
+ AND finding_id = ${requiredText(findingId, "Finding id")}
911
+ `;
912
+ return rows[0] ? parseVexApplication(rows[0].value) : null;
913
+ },
914
+ save: async (application) => saveVexApplications([application]),
915
+ saveMany: saveVexApplications
916
+ };
555
917
  const leases = {
556
918
  acquire: async (request) => {
557
919
  await ensureSchema();
@@ -594,9 +956,14 @@ var createPostgresVulnerabilityStore = (options) => {
594
956
  findings,
595
957
  leases,
596
958
  observations,
959
+ remediationExecutions,
960
+ remediationPlans,
961
+ remediationVerifications,
597
962
  riskAssessments,
598
963
  snapshots,
599
- syncRuns
964
+ syncRuns,
965
+ vexApplications,
966
+ vexDecisions
600
967
  };
601
968
  };
602
969
  export {
package/dist/manifest.js CHANGED
@@ -9,7 +9,7 @@ var manifest = defineManifest()({
9
9
  intents: [
10
10
  "persist vulnerability intelligence",
11
11
  "retain vulnerability sync history",
12
- "store managed vulnerability findings, observations, and risk assessments"
12
+ "store managed vulnerability findings, VEX decisions, remediation evidence, and risk assessments"
13
13
  ],
14
14
  keywords: ["vulnerabilities", "Postgres", "CVE", "feed-history"],
15
15
  protocols: ["PostgreSQL", "AbsoluteJS vulnerability contracts"]
@@ -17,7 +17,7 @@ var manifest = defineManifest()({
17
17
  identity: {
18
18
  accent: "#336791",
19
19
  category: "operations",
20
- description: "Durable Postgres snapshots, sync history, findings, observations, risk assessments, and distributed refresh leases.",
20
+ description: "Durable Postgres snapshots, findings, VEX decisions, remediation evidence, risk assessments, and distributed refresh leases.",
21
21
  docsUrl: "https://github.com/absolutejs/vulnerabilities-postgres",
22
22
  name: "@absolutejs/vulnerabilities-postgres",
23
23
  tagline: "Keep vulnerability intelligence durable and auditable."
@@ -30,7 +30,7 @@ var manifest = defineManifest()({
30
30
  requires: {
31
31
  env: [
32
32
  {
33
- description: "Postgres connection string for vulnerability intelligence, findings, observations, and risk assessments",
33
+ description: "Postgres connection string for vulnerability intelligence, findings, VEX decisions, remediation evidence, and risk assessments",
34
34
  example: "postgres://user:pass@host/db",
35
35
  key: "DATABASE_URL",
36
36
  secret: true
@@ -45,7 +45,7 @@ var manifest = defineManifest()({
45
45
  ],
46
46
  services: [
47
47
  {
48
- description: "Stores vulnerability intelligence, findings, observations, risk assessments, and leases",
48
+ description: "Stores vulnerability intelligence, findings, VEX decisions, remediation evidence, risk assessments, and leases",
49
49
  id: "postgres"
50
50
  }
51
51
  ]
@@ -8,7 +8,7 @@
8
8
  "intents": [
9
9
  "persist vulnerability intelligence",
10
10
  "retain vulnerability sync history",
11
- "store managed vulnerability findings, observations, and risk assessments"
11
+ "store managed vulnerability findings, VEX decisions, remediation evidence, and risk assessments"
12
12
  ],
13
13
  "keywords": [
14
14
  "vulnerabilities",
@@ -24,7 +24,7 @@
24
24
  "identity": {
25
25
  "accent": "#336791",
26
26
  "category": "operations",
27
- "description": "Durable Postgres snapshots, sync history, findings, observations, risk assessments, and distributed refresh leases.",
27
+ "description": "Durable Postgres snapshots, findings, VEX decisions, remediation evidence, risk assessments, and distributed refresh leases.",
28
28
  "docsUrl": "https://github.com/absolutejs/vulnerabilities-postgres",
29
29
  "name": "@absolutejs/vulnerabilities-postgres",
30
30
  "tagline": "Keep vulnerability intelligence durable and auditable."
@@ -37,7 +37,7 @@
37
37
  "requires": {
38
38
  "env": [
39
39
  {
40
- "description": "Postgres connection string for vulnerability intelligence, findings, observations, and risk assessments",
40
+ "description": "Postgres connection string for vulnerability intelligence, findings, VEX decisions, remediation evidence, and risk assessments",
41
41
  "example": "postgres://user:pass@host/db",
42
42
  "key": "DATABASE_URL",
43
43
  "secret": true
@@ -52,7 +52,7 @@
52
52
  ],
53
53
  "services": [
54
54
  {
55
- "description": "Stores vulnerability intelligence, findings, observations, risk assessments, and leases",
55
+ "description": "Stores vulnerability intelligence, findings, VEX decisions, remediation evidence, risk assessments, and leases",
56
56
  "id": "postgres"
57
57
  }
58
58
  ]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@absolutejs/vulnerabilities-postgres",
3
- "version": "0.3.0",
4
- "description": "Durable Postgres feeds, findings, observations, risk assessments, and refresh leases for AbsoluteJS vulnerability management.",
3
+ "version": "0.5.0",
4
+ "description": "Durable Postgres feeds, findings, VEX decisions, remediation evidence, risk assessments, and refresh leases for AbsoluteJS vulnerability management.",
5
5
  "type": "module",
6
6
  "license": "BSL-1.1",
7
7
  "author": "Alex Kahn",
@@ -53,7 +53,7 @@
53
53
  ],
54
54
  "dependencies": {
55
55
  "@absolutejs/manifest": "0.3.0",
56
- "@absolutejs/vulnerabilities": "0.7.0",
56
+ "@absolutejs/vulnerabilities": "0.8.0",
57
57
  "@sinclair/typebox": "0.34.52"
58
58
  },
59
59
  "peerDependencies": {