@amityco/social-plus-vise 0.14.11 → 0.14.12
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/CHANGELOG.md +10 -0
- package/README.md +24 -13
- package/dist/outcomes.js +1 -2
- package/dist/productExpectations.js +83 -0
- package/dist/tools/compliance.js +64 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to `@amityco/social-plus-vise` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 0.14.12 — 2026-06-05
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- **Multi-sensor shared expectations:** `comments.thread-read-write` now groups the concrete comment page-size, creation-affordance, observer-cleanup, and UI-state sensors under one public product expectation while preserving `contractRuleId` and `validator.sensorId` evidence.
|
|
11
|
+
- **Ambiguous attestation guidance:** when a shared public expectation maps to multiple contract rules in the same sidecar, `vise check` now emits exact `vise attest --rule <contract-rule-id>` commands and `vise attest --rule <public-id>` rejects with a concrete rule list.
|
|
12
|
+
- **Public benchmark claim:** README benchmark copy now describes the current full workflow, current release validation, and evidence boundaries without relying on local benchmark artifact links.
|
|
13
|
+
|
|
14
|
+
### Verified
|
|
15
|
+
- Packed-package smoke for `@amityco/social-plus-vise@0.14.12` confirmed plan question surfacing, design confirmation, optional feed capability sensors, shared comment expectation grouping, and ambiguous attestation handling.
|
|
16
|
+
|
|
7
17
|
## 0.14.11 — 2026-06-05
|
|
8
18
|
|
|
9
19
|
### Added
|
package/README.md
CHANGED
|
@@ -143,27 +143,40 @@ A bench vise holds the workpiece steady so the craftsman's hands are free to sha
|
|
|
143
143
|
|
|
144
144
|
## Benchmark Results: Current Claim
|
|
145
145
|
|
|
146
|
-
> **Vise gives AI coding agents a governed workflow for social.plus integrations,
|
|
146
|
+
> **Vise gives AI coding agents a governed workflow for social.plus integrations: it makes scope explicit, checks the local code, and turns missing SDK capabilities or compliance gaps into repair work before the agent stops.**
|
|
147
147
|
|
|
148
|
-
The strongest current claim is not a universal speed or quality promise. It is narrower and more useful:
|
|
148
|
+
The strongest current claim is not a universal speed or quality promise. It is narrower and more useful: for greenfield social.plus SDK work, Vise improves the stopping condition. The agent is not done after reading docs or producing code; it is done when the local contract is green, attested, or blocked on explicit customer input.
|
|
149
149
|
|
|
150
|
-
### Latest
|
|
150
|
+
### Latest Quantitative Benchmark
|
|
151
151
|
|
|
152
|
-
The latest
|
|
152
|
+
The latest feed-completeness benchmark remains the headline product proof. Same feed request, same SDK docs, no Vise workflow in the baseline; the Vise arm explicitly selected feed optional capabilities, persisted that choice into `sp-vise/compliance.json`, and activated selected source sensors.
|
|
153
153
|
|
|
154
|
-
| Agent / model | Docs-only baseline | Vise
|
|
154
|
+
| Agent / model | Docs-only baseline | Vise opt-in arm | Readout |
|
|
155
155
|
|---|---:|---:|---|
|
|
156
156
|
| Cursor / Composer 2.5 | 30% (3.3/11 avg) | **97% (32/33)** | One seed surfaced the remaining item instead of silently dropping it. |
|
|
157
157
|
| Claude / Sonnet 4.6 medium | 27% (3.0/11 avg) | **100% (33/33)** | All three Vise seeds reached 11/11. |
|
|
158
158
|
| Codex / GPT-5.4 medium | 21% (2.3/11 avg) | **100% (33/33)** | All three Vise seeds reached 11/11. |
|
|
159
159
|
|
|
160
|
-
Aggregate: **98/99 expected feed capabilities** and **27/27 selected optional capabilities** implemented across the
|
|
160
|
+
Aggregate: **98/99 expected feed capabilities** and **27/27 selected optional capabilities** implemented across the Vise arm.
|
|
161
161
|
|
|
162
|
-
###
|
|
162
|
+
### Current Release Validation
|
|
163
|
+
|
|
164
|
+
Version 0.14.12 adds current release proof around the full feed-forward and validation flow:
|
|
165
|
+
|
|
166
|
+
| Surface | What was validated |
|
|
167
|
+
|---|---|
|
|
168
|
+
| **Product flow** | Local end-to-end smoke covers design extraction, plan feed-forward, blocking intake, answered init, capability check, design conformance, and sensor discovery. |
|
|
169
|
+
| **Plan questions** | Plans surface blocking questions such as `feature_surface` and `design_contract_confirmation`, plus optional choices such as `feed_optional_capabilities`. |
|
|
170
|
+
| **Capability-to-sensor flow** | Vise checks platform support, matches the prompt to available capabilities, offers supported features as questions, records answers, and turns selected answers into sensors in `vise check`. |
|
|
171
|
+
| **Shared product expectations** | Public IDs such as `comments.thread-read-write` stay platform-agnostic while check results retain concrete `contractRuleId` and `validator.sensorId` evidence. |
|
|
172
|
+
| **Rule detection** | TP-track dashboard detects **311/311 seeded rule gaps (100.0%)** in the static corpus. |
|
|
173
|
+
| **Packed-package smoke** | A real Antigravity agent smoke tested the 0.14.12 tarball, opted into surfaced plan questions, repaired selected optional poll capability sensors, and verified ambiguous shared comment attestations require exact contract rule IDs. |
|
|
174
|
+
|
|
175
|
+
### Supporting Proof
|
|
163
176
|
|
|
164
177
|
| Surface | Safe claim | Evidence |
|
|
165
178
|
|---|---|---|
|
|
166
|
-
| **Feature completeness** | Vise helps agents build more of the expected SDK capability surface. | Latest comparison: **21-30% without Vise vs 97-100% with Vise
|
|
179
|
+
| **Feature completeness** | Vise helps agents build more of the expected SDK capability surface. | Latest comparison: **21-30% without Vise vs 97-100% with Vise**, with **98/99** expected feed capabilities implemented in aggregate. Earlier Capability Matrix work also showed silently dropped items falling from 7.67/11 to 4.0/11. |
|
|
167
180
|
| **SDK compliance** | Vise checks catch greenfield SDK compliance gaps that docs or static guidance can miss. | Commune benchmark: Vise averaged **100% greenfield SDK compliance** where docs/RAG-style controls averaged **67%** across the reported slices. |
|
|
168
181
|
| **Design conformance** | Vise design checks reduce design drift under ambiguous briefs. | Ambiguous Spotify-style design test: Vise design runs produced **0 / 0 / 0 hex literals** across three seeds; without Vise, runs varied **0 / 2 / 15**. This supports variance reduction, not pixel-perfect visual quality. |
|
|
169
182
|
|
|
@@ -172,10 +185,10 @@ Aggregate: **98/99 expected feed capabilities** and **27/27 selected optional ca
|
|
|
172
185
|
The benchmark story is the product flow:
|
|
173
186
|
|
|
174
187
|
1. **Inspect** — Vise detects platform, app surface, SDK surface, sensors, and design signals from the local repo.
|
|
175
|
-
2. **Plan** — Vise classifies the outcome, asks blocking intake questions,
|
|
188
|
+
2. **Plan** — Vise classifies the outcome, asks blocking intake questions, checks platform capability availability, and offers optional features only when the platform SDK surface supports them.
|
|
176
189
|
3. **Confirm design** — `vise design extract` writes a preview; `plan`/`init` withhold design feed-forward until the user confirms `design_contract_confirmation=yes`.
|
|
177
190
|
4. **Initialize** — `vise init` records the resolved compliance contract, intake answers, selected optional capabilities, inspection result, and accepted design digest.
|
|
178
|
-
5. **Build / check / repair** —
|
|
191
|
+
5. **Build / check / repair** — selected answers become sensors. The agent edits locally, runs `vise check`, fixes deterministic findings, resolves completeness gaps or selected-capability failures, and then runs project sensors.
|
|
179
192
|
|
|
180
193
|
Static docs can tell an agent what exists. Vise changes the stopping condition: the agent is not done until the local contract is green, attested, or blocked on explicit customer input.
|
|
181
194
|
|
|
@@ -183,14 +196,12 @@ Static docs can tell an agent what exists. Vise changes the stopping condition:
|
|
|
183
196
|
|
|
184
197
|
The benchmark suite is intentionally reported with boundaries:
|
|
185
198
|
|
|
186
|
-
- **Latest feed-completeness comparison** is the current product claim for the opt-in capability flow
|
|
199
|
+
- **Latest feed-completeness comparison** is the current quantitative product claim for the opt-in capability flow. It is a best-case/opt-in comparison across Cursor, Claude, and Codex, not a universal result for every prompt.
|
|
187
200
|
- **Capability Matrix v1** remains the pre-registered follow-up. It shipped the Row 2 feature-completeness claim, found **no Row 1 SDK-compliance claim** on chat/moderation/push under its registered margin, and withheld the Row 3 design claim on a technicality despite higher by-name token use.
|
|
188
201
|
- **Commune Phase 1** remains useful historical evidence for the compliance loop: two agents reached 9/9 with Vise vs 5-7/9 under controls, but it was N=1 per cell and the grader overlaps Vise's own rules.
|
|
189
202
|
- **Design tests** support design-drift reduction and token cleanup. They do not prove visual taste, pixel perfection, or production-ready UI without human review.
|
|
190
203
|
- **Negative results must travel with the claim:** no measured Vise advantage on day-2 bug fixing; the push slice exposed a non-converging attestation loop when docs and SDK disagreed; earlier enumerative plan-time design guidance measured negative and was retracted; the original `scope-omit` affordance went unused in the matrix.
|
|
191
204
|
|
|
192
|
-
Full evidence: [`benchmarks/CURSOR_VISE_0.14.5_RESULTS.html`](benchmarks/CURSOR_VISE_0.14.5_RESULTS.html), [`benchmarks/capability-matrix/RESULTS.md`](benchmarks/capability-matrix/RESULTS.md), [`benchmarks/commune/RESULTS.md`](benchmarks/commune/RESULTS.md), and [`benchmarks/brand/design-test/RESULTS.md`](benchmarks/brand/design-test/RESULTS.md).
|
|
193
|
-
|
|
194
205
|
### Which mode should I use?
|
|
195
206
|
|
|
196
207
|
| If you… | Use | Why |
|
package/dist/outcomes.js
CHANGED
|
@@ -788,8 +788,7 @@ const addComments = {
|
|
|
788
788
|
"comment target resolved",
|
|
789
789
|
"no invented postId/commentId",
|
|
790
790
|
`${platform}.comments.target-resolved`,
|
|
791
|
-
|
|
792
|
-
`${platform}.comments.ui-states-present`,
|
|
791
|
+
"comments.thread-read-write",
|
|
793
792
|
`${platform}.comments.moderation-affordance-present`,
|
|
794
793
|
],
|
|
795
794
|
stopConditions: (ctx) => filterStops(ctx.answers, [
|
|
@@ -49,31 +49,111 @@ export const PRODUCT_EXPECTATION_BINDINGS = [
|
|
|
49
49
|
sensorId: "ios.feed.rich-post-composer-surfaced",
|
|
50
50
|
platform: "ios",
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
expectationId: "comments.thread-read-write",
|
|
54
|
+
sensorId: "typescript.comments.query-has-limit",
|
|
55
|
+
platform: "typescript",
|
|
56
|
+
},
|
|
52
57
|
{
|
|
53
58
|
expectationId: "comments.thread-read-write",
|
|
54
59
|
sensorId: "typescript.comments.creation-affordance-present",
|
|
55
60
|
platform: "typescript",
|
|
56
61
|
},
|
|
62
|
+
{
|
|
63
|
+
expectationId: "comments.thread-read-write",
|
|
64
|
+
sensorId: "typescript.comments.observer-cleanup",
|
|
65
|
+
platform: "typescript",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
expectationId: "comments.thread-read-write",
|
|
69
|
+
sensorId: "typescript.comments.ui-states-present",
|
|
70
|
+
platform: "typescript",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
expectationId: "comments.thread-read-write",
|
|
74
|
+
sensorId: "react-native.comments.query-has-limit",
|
|
75
|
+
platform: "react-native",
|
|
76
|
+
},
|
|
57
77
|
{
|
|
58
78
|
expectationId: "comments.thread-read-write",
|
|
59
79
|
sensorId: "react-native.comments.creation-affordance-present",
|
|
60
80
|
platform: "react-native",
|
|
61
81
|
},
|
|
82
|
+
{
|
|
83
|
+
expectationId: "comments.thread-read-write",
|
|
84
|
+
sensorId: "react-native.comments.observer-cleanup",
|
|
85
|
+
platform: "react-native",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
expectationId: "comments.thread-read-write",
|
|
89
|
+
sensorId: "react-native.comments.ui-states-present",
|
|
90
|
+
platform: "react-native",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
expectationId: "comments.thread-read-write",
|
|
94
|
+
sensorId: "android.comments.query-has-limit",
|
|
95
|
+
platform: "android",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
expectationId: "comments.thread-read-write",
|
|
99
|
+
sensorId: "android.comments.creation-affordance-present",
|
|
100
|
+
platform: "android",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
expectationId: "comments.thread-read-write",
|
|
104
|
+
sensorId: "android.comments.observer-cleanup",
|
|
105
|
+
platform: "android",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
expectationId: "comments.thread-read-write",
|
|
109
|
+
sensorId: "android.comments.ui-states-present",
|
|
110
|
+
platform: "android",
|
|
111
|
+
},
|
|
62
112
|
{
|
|
63
113
|
expectationId: "comments.thread-read-write",
|
|
64
114
|
sensorId: "android.comments.thread-ui-states-present",
|
|
65
115
|
platform: "android",
|
|
66
116
|
},
|
|
117
|
+
{
|
|
118
|
+
expectationId: "comments.thread-read-write",
|
|
119
|
+
sensorId: "flutter.comments.query-has-limit",
|
|
120
|
+
platform: "flutter",
|
|
121
|
+
},
|
|
67
122
|
{
|
|
68
123
|
expectationId: "comments.thread-read-write",
|
|
69
124
|
sensorId: "flutter.comments.creation-affordance-present",
|
|
70
125
|
platform: "flutter",
|
|
71
126
|
},
|
|
127
|
+
{
|
|
128
|
+
expectationId: "comments.thread-read-write",
|
|
129
|
+
sensorId: "flutter.comments.observer-cleanup",
|
|
130
|
+
platform: "flutter",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
expectationId: "comments.thread-read-write",
|
|
134
|
+
sensorId: "flutter.comments.ui-states-present",
|
|
135
|
+
platform: "flutter",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
expectationId: "comments.thread-read-write",
|
|
139
|
+
sensorId: "ios.comments.query-has-limit",
|
|
140
|
+
platform: "ios",
|
|
141
|
+
},
|
|
72
142
|
{
|
|
73
143
|
expectationId: "comments.thread-read-write",
|
|
74
144
|
sensorId: "ios.comments.creation-affordance-present",
|
|
75
145
|
platform: "ios",
|
|
76
146
|
},
|
|
147
|
+
{
|
|
148
|
+
expectationId: "comments.thread-read-write",
|
|
149
|
+
sensorId: "ios.comments.observer-cleanup",
|
|
150
|
+
platform: "ios",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
expectationId: "comments.thread-read-write",
|
|
154
|
+
sensorId: "ios.comments.ui-states-present",
|
|
155
|
+
platform: "ios",
|
|
156
|
+
},
|
|
77
157
|
{
|
|
78
158
|
expectationId: "chat.unread-visible",
|
|
79
159
|
sensorId: "typescript.chat.unread-visible",
|
|
@@ -169,3 +249,6 @@ export function contractRuleCandidatesForPublicId(ruleId) {
|
|
|
169
249
|
.filter((binding) => binding.expectationId === ruleId)
|
|
170
250
|
.map((binding) => binding.sensorId);
|
|
171
251
|
}
|
|
252
|
+
export function hasMultipleContractRuleCandidates(ruleId) {
|
|
253
|
+
return contractRuleCandidatesForPublicId(ruleId).length > 1;
|
|
254
|
+
}
|
package/dist/tools/compliance.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { assessProjectCompleteness, assessProjectSelectedOptionalCapabilities, availableOptionalCapabilityIds, optionalCapabilityChecklist, platformCapabilityAvailability, selectedOptionalCapabilityIds, } from "../capabilities.js";
|
|
6
6
|
import { getOutcomeDefinition, hasAnswer, planContextFor, resolveOutcome, } from "../outcomes.js";
|
|
7
|
-
import { contractRuleCandidatesForPublicId, productExpectationBindingForSensor, publicProductRuleId, } from "../productExpectations.js";
|
|
7
|
+
import { contractRuleCandidatesForPublicId, hasMultipleContractRuleCandidates, productExpectationBindingForSensor, publicProductRuleId, } from "../productExpectations.js";
|
|
8
8
|
import { objectInput, optionalBooleanField, optionalStringField, stringField, textResult } from "../types.js";
|
|
9
9
|
import { packageVersion } from "../version.js";
|
|
10
10
|
import { DESIGN_CONTRACT_CONFIRMATION_ANSWER_ID, buildDesignBrief, designContractConfirmationFromAnswers, designPreviewPath, readDesignContract, } from "./design.js";
|
|
@@ -568,7 +568,7 @@ export async function checkCompliance(repoPath) {
|
|
|
568
568
|
recommendation: finding?.recommendation,
|
|
569
569
|
rationale: rule.rationale,
|
|
570
570
|
current_rule: ruleSummary(rule),
|
|
571
|
-
...(failStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule)),
|
|
571
|
+
...(failStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule, compliance)),
|
|
572
572
|
});
|
|
573
573
|
continue;
|
|
574
574
|
}
|
|
@@ -593,7 +593,7 @@ export async function checkCompliance(repoPath) {
|
|
|
593
593
|
rationale: rule.rationale,
|
|
594
594
|
current_rule: ruleSummary(rule),
|
|
595
595
|
source_fingerprint_status: sourceFingerprintStatus,
|
|
596
|
-
...(fingerprintStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule)),
|
|
596
|
+
...(fingerprintStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule, compliance)),
|
|
597
597
|
});
|
|
598
598
|
continue;
|
|
599
599
|
}
|
|
@@ -639,7 +639,7 @@ export async function checkCompliance(repoPath) {
|
|
|
639
639
|
recommendation: finding?.recommendation,
|
|
640
640
|
current_rule: ruleSummary(rule),
|
|
641
641
|
...(isInferential && { inferential_prompt: rule.enforcement.inferential?.prompt }),
|
|
642
|
-
...(baseStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule)),
|
|
642
|
+
...(baseStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule, compliance)),
|
|
643
643
|
});
|
|
644
644
|
}
|
|
645
645
|
const summary = summarize(results);
|
|
@@ -738,6 +738,14 @@ export async function attestRule(args) {
|
|
|
738
738
|
const contractRuleId = resolveRuleIdForContract(args.ruleId, compliance, rules);
|
|
739
739
|
const rule = contractRuleId ? rules.get(contractRuleId) : undefined;
|
|
740
740
|
if (!rule || !contractRuleId) {
|
|
741
|
+
const ambiguousCandidates = contractRuleCandidatesForContract(args.ruleId, compliance, rules);
|
|
742
|
+
if (ambiguousCandidates.length > 1) {
|
|
743
|
+
const candidateIds = ambiguousCandidates
|
|
744
|
+
.filter((candidate) => rules.get(candidate)?.enforcement.attestation.allowed)
|
|
745
|
+
.slice(0, 8);
|
|
746
|
+
const ids = candidateIds.length > 0 ? candidateIds : ambiguousCandidates.slice(0, 8);
|
|
747
|
+
throw new Error(`Rule is ambiguous in this compliance contract: ${args.ruleId}. Attest one contract rule at a time: ${ids.join(", ")}.`);
|
|
748
|
+
}
|
|
741
749
|
// Collect up to 8 applicable attestable rule ids from this contract for the error hint. Prefer
|
|
742
750
|
// ids that share the bad id's non-wildcard prefix so the agent can narrow down quickly.
|
|
743
751
|
const attestableIds = compliance.rules
|
|
@@ -746,7 +754,10 @@ export async function attestRule(args) {
|
|
|
746
754
|
const prefix = args.ruleId.replace(/\.\*$|\*$/, "");
|
|
747
755
|
const prefixed = prefix !== args.ruleId ? attestableIds.filter((r) => r.id.startsWith(prefix) || r.id.includes(prefix) || publicProductRuleId(r.id).startsWith(prefix)) : [];
|
|
748
756
|
const candidates = prefixed.length > 0 ? prefixed : attestableIds;
|
|
749
|
-
const hintIds = candidates.slice(0, 8).map((r) =>
|
|
757
|
+
const hintIds = uniqueStrings(candidates.slice(0, 8).map((r) => {
|
|
758
|
+
const publicId = publicProductRuleId(r.id);
|
|
759
|
+
return publicId === r.id ? r.id : `${publicId} (${r.id})`;
|
|
760
|
+
}));
|
|
750
761
|
const hintSuffix = hintIds.length > 0 ? ` Applicable attestable rules: ${hintIds.join(", ")}.` : " Applicable attestable rules: none.";
|
|
751
762
|
const preamble = args.ruleId.includes("*")
|
|
752
763
|
? `Wildcards are not supported — attest one rule at a time.`
|
|
@@ -780,6 +791,31 @@ export async function explainRule(ruleId) {
|
|
|
780
791
|
const rules = await rulesById();
|
|
781
792
|
const contractRuleId = resolveRuleIdForInstalledRules(ruleId, rules);
|
|
782
793
|
const rule = contractRuleId ? rules.get(contractRuleId) : undefined;
|
|
794
|
+
const publicCandidates = contractRuleCandidatesForPublicId(ruleId)
|
|
795
|
+
.map((candidate) => rules.get(candidate))
|
|
796
|
+
.filter((candidate) => candidate !== undefined);
|
|
797
|
+
if (!rule && publicCandidates.length > 1) {
|
|
798
|
+
return {
|
|
799
|
+
id: ruleId,
|
|
800
|
+
kind: "product_expectation",
|
|
801
|
+
note: "This public product expectation is validated by multiple concrete contract rules. Use the contract_rule_id when attesting one sensor.",
|
|
802
|
+
contract_rules: publicCandidates.map((candidate) => {
|
|
803
|
+
const binding = productExpectationBindingForSensor(candidate.id);
|
|
804
|
+
return {
|
|
805
|
+
contract_rule_id: candidate.id,
|
|
806
|
+
...(binding && { validator: { platform: binding.platform, sensorId: binding.sensorId } }),
|
|
807
|
+
version: candidate.version,
|
|
808
|
+
title: candidate.title,
|
|
809
|
+
severity: candidate.severity,
|
|
810
|
+
rationale: candidate.rationale,
|
|
811
|
+
applies_when: candidate.applies_when,
|
|
812
|
+
attestation: candidate.enforcement.attestation,
|
|
813
|
+
summary: ruleSummary(candidate),
|
|
814
|
+
rule_digest: digestRule(candidate),
|
|
815
|
+
};
|
|
816
|
+
}),
|
|
817
|
+
};
|
|
818
|
+
}
|
|
783
819
|
if (!rule || !contractRuleId) {
|
|
784
820
|
throw new Error(`Unknown compliance rule: ${ruleId}`);
|
|
785
821
|
}
|
|
@@ -865,9 +901,12 @@ function resolveRuleIdForContract(ruleId, compliance, rules) {
|
|
|
865
901
|
if (rules.has(ruleId) && compliance.rules.some((ref) => ref.rule_id === ruleId)) {
|
|
866
902
|
return ruleId;
|
|
867
903
|
}
|
|
868
|
-
const candidates =
|
|
904
|
+
const candidates = contractRuleCandidatesForContract(ruleId, compliance, rules);
|
|
869
905
|
return candidates.length === 1 ? candidates[0] : null;
|
|
870
906
|
}
|
|
907
|
+
function contractRuleCandidatesForContract(ruleId, compliance, rules) {
|
|
908
|
+
return contractRuleCandidatesForPublicId(ruleId).filter((candidate) => rules.has(candidate) && compliance.rules.some((ref) => ref.rule_id === candidate));
|
|
909
|
+
}
|
|
871
910
|
function resolveRuleIdForInstalledRules(ruleId, rules) {
|
|
872
911
|
if (rules.has(ruleId)) {
|
|
873
912
|
return ruleId;
|
|
@@ -895,12 +934,16 @@ function ruleRefForFile(rule) {
|
|
|
895
934
|
}
|
|
896
935
|
// Benchmark-measured friction: agents looped on attest dialect for ~25 min/cell when docs and SDK
|
|
897
936
|
// disagreed on exact invocation syntax (capability-matrix 2026-06, Row 5). Hand them the exact incantation.
|
|
898
|
-
function attestHint(rule) {
|
|
937
|
+
function attestHint(rule, compliance) {
|
|
899
938
|
const minConfidence = rule.enforcement.attestation.host_agent_min_confidence ?? "high";
|
|
900
939
|
const fields = rule.enforcement.attestation.evidence_required ?? [];
|
|
901
940
|
const publicRuleId = publicProductRuleId(rule.id);
|
|
941
|
+
const candidatesInContract = compliance
|
|
942
|
+
? contractRuleCandidatesForPublicId(publicRuleId).filter((candidate) => compliance.rules.some((ref) => ref.rule_id === candidate))
|
|
943
|
+
: contractRuleCandidatesForPublicId(publicRuleId);
|
|
944
|
+
const ruleArg = hasMultipleContractRuleCandidates(publicRuleId) && candidatesInContract.length > 1 ? rule.id : publicRuleId;
|
|
902
945
|
return {
|
|
903
|
-
attest_command: `vise attest --rule ${
|
|
946
|
+
attest_command: `vise attest --rule ${ruleArg} --confidence ${minConfidence} --signer host-agent --evidence-file sp-vise/evidence/${rule.id}.json --rationale "<why this rule is satisfied (or cannot apply) in this codebase>"`,
|
|
904
947
|
evidence_template: Object.fromEntries(fields.map((f) => [f.field, `<${f.description}>`])),
|
|
905
948
|
};
|
|
906
949
|
}
|
|
@@ -939,7 +982,7 @@ function contractDrift(compliance, rules) {
|
|
|
939
982
|
status: "stale",
|
|
940
983
|
reason,
|
|
941
984
|
current_rule: ruleSummary(rule),
|
|
942
|
-
...(rule.enforcement.attestation.allowed && attestHint(rule)),
|
|
985
|
+
...(rule.enforcement.attestation.allowed && attestHint(rule, compliance)),
|
|
943
986
|
});
|
|
944
987
|
}
|
|
945
988
|
}
|
|
@@ -959,6 +1002,18 @@ function deterministicFindingIds(rule) {
|
|
|
959
1002
|
.filter((check) => check.check === "validator-finding-absent")
|
|
960
1003
|
.map((check) => check.finding_rule_id);
|
|
961
1004
|
}
|
|
1005
|
+
function uniqueStrings(values) {
|
|
1006
|
+
const seen = new Set();
|
|
1007
|
+
const result = [];
|
|
1008
|
+
for (const value of values) {
|
|
1009
|
+
if (seen.has(value)) {
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
seen.add(value);
|
|
1013
|
+
result.push(value);
|
|
1014
|
+
}
|
|
1015
|
+
return result;
|
|
1016
|
+
}
|
|
962
1017
|
function buildAttestation(compliance, rule, signer, confidence, identity, rationale, evidence, sourceFingerprints = []) {
|
|
963
1018
|
const ref = compliance.rules.find((item) => item.rule_id === rule.id);
|
|
964
1019
|
if (!ref) {
|
package/package.json
CHANGED