@amityco/social-plus-vise 0.8.1 → 0.12.2
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 +207 -0
- package/README.md +107 -40
- package/dist/capabilities.js +447 -0
- package/dist/outcomes.js +463 -5
- package/dist/server.js +115 -3
- package/dist/tools/ast.js +25 -0
- package/dist/tools/compliance.js +88 -20
- package/dist/tools/debug.js +267 -0
- package/dist/tools/design.js +1496 -0
- package/dist/tools/docs.js +9 -4
- package/dist/tools/harness.js +17 -1
- package/dist/tools/integration.js +83 -7
- package/dist/tools/project.js +872 -67
- package/dist/tools/sdkVersion.js +129 -0
- package/dist/types.js +4 -0
- package/package.json +27 -6
- package/rules/auth.yaml +298 -38
- package/rules/comments.yaml +0 -72
- package/rules/feed.yaml +1151 -12
- package/rules/live-data.yaml +316 -36
- package/rules/push.yaml +140 -0
- package/rules/sdk-lifecycle.yaml +1428 -138
- package/rules/security.yaml +60 -0
- package/skills/social-plus-vise/SKILL.md +98 -55
- package/skills/social-plus-vise/reference/debugging.md +39 -0
- package/skills/social-plus-vise/reference/operations.md +59 -0
- package/skills/vise-harness-engineer/SKILL.md +35 -0
- package/social.plus-vise.png +0 -0
package/rules/security.yaml
CHANGED
|
@@ -217,6 +217,66 @@
|
|
|
217
217
|
"deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "react-native.logging.no-pii-in-log" }],
|
|
218
218
|
"attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "redaction_policy", "description": "Explanation of how PII is redacted or why the logged value is not real user PII.", "upload_policy": "upload-with-consent" }] }
|
|
219
219
|
}
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"id": "react-native.secret.env-gitignore",
|
|
223
|
+
"version": 1,
|
|
224
|
+
"title": "React Native env secret files should be ignored",
|
|
225
|
+
"severity": "warning",
|
|
226
|
+
"rationale": "When SDK secrets are loaded from env files, local env files should be ignored so customer API keys are not accidentally committed.",
|
|
227
|
+
"applies_when": { "platforms": ["react-native"], "outcomes": ["setup-sdk", "setup-push", "setup-live-data", "add-feed", "validate-setup"] },
|
|
228
|
+
"enforcement": {
|
|
229
|
+
"deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "react-native.secret.env-gitignore" }],
|
|
230
|
+
"attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "gitignore_policy", "description": "Where local env files are ignored or otherwise prevented from commit.", "upload_policy": "upload-with-consent" }] }
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"id": "react-native.secret.env-example",
|
|
235
|
+
"version": 1,
|
|
236
|
+
"title": "React Native env secret config should have an example file",
|
|
237
|
+
"severity": "warning",
|
|
238
|
+
"rationale": "A placeholder env example lets agents and developers create local config without asking users to paste secrets into chat.",
|
|
239
|
+
"applies_when": { "platforms": ["react-native"], "outcomes": ["setup-sdk", "setup-push", "setup-live-data", "add-feed", "validate-setup"] },
|
|
240
|
+
"enforcement": {
|
|
241
|
+
"deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "react-native.secret.env-example" }],
|
|
242
|
+
"attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "template_path", "description": "Path to the committed env/config template with placeholder secret keys.", "upload_policy": "upload-with-consent" }] }
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"id": "flutter.secret.env-gitignore",
|
|
247
|
+
"version": 1,
|
|
248
|
+
"title": "Flutter env secret files should be ignored",
|
|
249
|
+
"severity": "warning",
|
|
250
|
+
"rationale": "When SDK secrets are loaded from .env or secrets.dart, they should be ignored so customer API keys are not accidentally committed.",
|
|
251
|
+
"applies_when": { "platforms": ["flutter"], "outcomes": ["setup-sdk", "setup-push", "setup-live-data", "add-feed", "validate-setup"] },
|
|
252
|
+
"enforcement": {
|
|
253
|
+
"deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "flutter.secret.env-gitignore" }],
|
|
254
|
+
"attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "gitignore_policy", "description": "Where local env files are ignored or otherwise prevented from commit.", "upload_policy": "upload-with-consent" }] }
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"id": "android.secret.env-gitignore",
|
|
259
|
+
"version": 1,
|
|
260
|
+
"title": "Android env secret files should be ignored",
|
|
261
|
+
"severity": "warning",
|
|
262
|
+
"rationale": "local.properties should be ignored so customer API keys are not accidentally committed.",
|
|
263
|
+
"applies_when": { "platforms": ["android"], "outcomes": ["setup-sdk", "setup-push", "setup-live-data", "add-feed", "validate-setup"] },
|
|
264
|
+
"enforcement": {
|
|
265
|
+
"deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "android.secret.env-gitignore" }],
|
|
266
|
+
"attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "gitignore_policy", "description": "Where local env files are ignored or otherwise prevented from commit.", "upload_policy": "upload-with-consent" }] }
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"id": "ios.secret.env-gitignore",
|
|
271
|
+
"version": 1,
|
|
272
|
+
"title": "iOS env secret files should be ignored",
|
|
273
|
+
"severity": "warning",
|
|
274
|
+
"rationale": "Secrets.plist and .xcconfig should be ignored so customer API keys are not accidentally committed.",
|
|
275
|
+
"applies_when": { "platforms": ["ios"], "outcomes": ["setup-sdk", "setup-push", "setup-live-data", "add-feed", "validate-setup"] },
|
|
276
|
+
"enforcement": {
|
|
277
|
+
"deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "ios.secret.env-gitignore" }],
|
|
278
|
+
"attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "gitignore_policy", "description": "Where local env files are ignored or otherwise prevented from commit.", "upload_policy": "upload-with-consent" }] }
|
|
279
|
+
}
|
|
220
280
|
}
|
|
221
281
|
]
|
|
222
282
|
}
|
|
@@ -35,30 +35,23 @@ vise validate .
|
|
|
35
35
|
vise run-sensors .
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
**`vise check .` is mandatory, not optional. You are not done until it passes or every finding is explicitly attested.** Running `vise plan` and `vise inspect` but skipping `vise check` is the most common failure mode — it means the deterministic catch-net never ran and known-bad patterns ship. When you read a `vise plan`, do not truncate it (`| head` drops the implementation steps); read the full `implementationSteps` array.
|
|
39
|
+
|
|
38
40
|
Treat Vise runtime smoke sensors as real validation. For TypeScript/React Native projects, `vise run-sensors` may include `TypeScript SDK import smoke`; if it fails, the SDK package does not resolve from the host project runtime and the integration is not done.
|
|
39
41
|
|
|
40
42
|
In CI or pull-request pipelines, use `vise check . --ci`. It is read-only, exits non-zero unless compliance is green, and never writes deterministic-pass records; use `vise sync` only during the implementation loop after a local green check.
|
|
41
43
|
|
|
42
|
-
##
|
|
43
|
-
|
|
44
|
-
If the customer has set up `sp-vise/engagement.json` (via `vise engagement init`), respect the declared scope. `vise engagement show .` prints the engagement metadata: tier, customer id, contracted outcomes, reviewer assignment.
|
|
45
|
-
|
|
46
|
-
When `vise init` reports a `warnings` array containing "not in the engagement scope", surface that to the user — don't silently widen scope. The user either extends the scope by editing `engagement.json` or accepts that the current work is out-of-contract.
|
|
47
|
-
|
|
48
|
-
If no `engagement.json` exists, do not create one without the user's intent. `vise engagement init` is a contractual artifact; agents should only run it when the user explicitly asks to set up engagement metadata, typically with `--tier`, `--customer-id`, and `--scope`.
|
|
44
|
+
## Debugging (Day 2 Operations)
|
|
49
45
|
|
|
50
|
-
If `vise`
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
npm install -g @amityco/social-plus-foundry
|
|
54
|
-
```
|
|
46
|
+
If the customer reports an **SDK-specific** runtime crash, build failure, or data-not-loading bug, start with `vise debug .` (with `--error` / `--error-file`) **before** broad repo inspection — do not guess the fix first. Scope limit: only for errors mentioning `social.plus`/`Amity`/`live-collection` or in files importing the SDK; never for generic UI/CSS issues. Full `vise debug` workflow and `repairBrief` format: **see `reference/debugging.md`**.
|
|
55
47
|
|
|
56
|
-
|
|
48
|
+
(If `vise` isn't installed: `npm install -g @amityco/social-plus-vise`. Engagement-scope handling — `sp-vise/engagement.json` — is in `reference/operations.md`.)
|
|
57
49
|
|
|
58
50
|
## Stop Instead Of Guessing
|
|
59
51
|
|
|
60
52
|
Ask the user before editing when required values are missing:
|
|
61
53
|
|
|
54
|
+
- Next.js rendering mode: whether the integration target is a Server Component, Client Component (`'use client'`), or Pages Router. SDK initialization must be client-side only.
|
|
62
55
|
- region, auth/user identity, or session-handler ownership (which class/module implements `sessionWillRenewAccessToken`)
|
|
63
56
|
- feed target, community ID, target ID, feed ID, or channel ID
|
|
64
57
|
- comment target source (which parent entity provides postId/referenceId)
|
|
@@ -109,6 +102,8 @@ Per platform:
|
|
|
109
102
|
- Flutter Dart: implement `AmitySessionHandler.sessionWillRenewAccessToken(AmityAccessTokenRenewal)` and call `renewal.renew()`.
|
|
110
103
|
- iOS Swift: implement `AmitySessionHandler.sessionWillRenewAccessToken(renewal:)` and call `renewal.renew()`.
|
|
111
104
|
|
|
105
|
+
**Do not fire SDK collection queries in the same synchronous tick as `login()`.** Gate collection setup behind the SDK's session-active signal (`sessionWillRenewAccessToken` confirming the first token was granted, or an explicit auth-state callback). On a warm local machine login resolves first by chance; on a cold production start the query fires before the session is established and silently returns empty or errors.
|
|
106
|
+
|
|
112
107
|
### Session Handler Retention
|
|
113
108
|
|
|
114
109
|
**CRITICAL:** The session handler object must outlive the function that created it. If you declare the session handler as a function-local variable (e.g. inside `setUp()`, `initState()`, `onCreate()`), the host language's garbage collector (Dart, JS, Kotlin) or ARC (iOS) will destroy it before the SDK ever needs to renew the session.
|
|
@@ -134,6 +129,15 @@ Amity exposes a global `unreadCount` stream that syncs across devices.
|
|
|
134
129
|
- **Correct pattern**: Subscribe to the unread stream provided by the SDK (e.g. `AmityCoreClient.unreadCount()`, `getUnreadCount()`, `unread.stream`).
|
|
135
130
|
- **Wrong pattern**: Hand-rolling unread counting on the client (e.g. `posts.filter(p => !p.isRead).length`). This causes counts to go out of sync because the server's read state isn't pushed to the manual counter.
|
|
136
131
|
|
|
132
|
+
A read *count* with no way to clear it is half a feature: when a channel/thread is opened, mark it read so the server-side count actually decrements (`channel.markAsRead()` / `message.markRead()`, or `startMessageReceiptSync` + `stopMessageReceiptSync` paired with the view lifecycle). Reading the unread stream but never marking read leaves the badge stuck.
|
|
133
|
+
|
|
134
|
+
### Message Delivery State
|
|
135
|
+
|
|
136
|
+
A sent message starts in a `syncing` state and can land in `error` (e.g. network failure, or after an SDK session restart). A composer that renders only the optimistic success leaves failed messages silently stuck.
|
|
137
|
+
|
|
138
|
+
- **Correct pattern**: Observe each message's `syncState` and surface failed sends with a retry/delete affordance; clean up unrecoverable failures (e.g. `deleteFailedMessages()`) on init.
|
|
139
|
+
- **Wrong pattern**: Calling `createMessage` and rendering success unconditionally, with no handling for the `error` syncState.
|
|
140
|
+
|
|
137
141
|
### File Upload & Media
|
|
138
142
|
|
|
139
143
|
Uploading media to external buckets (S3, Cloudinary) directly and trying to attach the resulting URL to an Amity post will fail. The SDK won't recognize external URLs as attachments.
|
|
@@ -155,19 +159,42 @@ When querying lists of entities (e.g., `getPosts`, `getCommunityFeed`), always u
|
|
|
155
159
|
- **Correct pattern:** Observe the collection (e.g., `.observe()`, `.listen()`, `StreamBuilder`, `LiveData`, `getLiveCollection()`) and render the stream.
|
|
156
160
|
- **Wrong pattern:** `await getPosts(...)` and assigning to a static array.
|
|
157
161
|
|
|
162
|
+
When using a LiveCollection or live object, also handle connection-state changes. The SDK exposes a connection state stream; render a "reconnecting" indicator or disable interaction controls when the connection is lost. An app that silently shows stale data on network drops generates support tickets attributed to the SDK rather than the integration.
|
|
163
|
+
|
|
158
164
|
### Post Status Filters
|
|
159
165
|
Always filter post queries to exclude deleted or flagged content. Amity's older SDKs include flagged/deleted posts by default, which can leak moderated content into the feed UI if unfiltered.
|
|
160
166
|
- **Correct pattern:** Apply `.includeDeleted(false)`, `.statuses([.published])`, or `.feedType(AmityFeedType.PUBLISHED)` to your query builders.
|
|
161
167
|
|
|
168
|
+
### Post Review Queue
|
|
169
|
+
When a community has review enabled (`ADMIN_REVIEW_POST_REQUIRED`), posts flow through `reviewing` → `published`/`declined` feeds. A moderation dashboard that only queries the `published` feed silently misses everything awaiting review.
|
|
170
|
+
- **Correct pattern:** For a moderator surface, query `feedType: 'reviewing'` (gated on the `REVIEW_COMMUNITY_POST` permission) and wire `approvePost()` / `declinePost()`. A regular member feed stays on `published`.
|
|
171
|
+
- This is completeness, not deterministically enforced (most feeds aren't moderator dashboards) — match it to whether you're building a moderation/review surface.
|
|
172
|
+
|
|
162
173
|
### Opaque Pagination Cursors
|
|
163
174
|
Amity uses opaque, token-based cursors for pagination. Never attempt to construct the `nextPage` argument using numeric math (e.g., `page * size`). This will cause silent failures or HTTP 400 errors.
|
|
164
175
|
- **Correct pattern:** Pass the exact `nextPageToken` returned from the previous query result, or call `.loadMore()` / `.nextPage()` on the collection itself.
|
|
165
176
|
- **Wrong pattern:** `.nextPage(pageNumber * 20)`
|
|
166
177
|
|
|
167
178
|
### Parent-Child Post Rendering
|
|
168
|
-
Amity
|
|
169
|
-
- **Correct pattern:**
|
|
170
|
-
- **Wrong pattern:** `Text(post.data.text)` with no
|
|
179
|
+
Amity attaches rich content — **images, videos, files, polls, and rooms/livestreams** — as "child posts" under a parent whose own `dataType` is usually `'text'`. A UI that reads only the parent will silently drop all of it. This applies to **every** media type, not just images: resolving images from `childrenPosts` but gating polls/videos/rooms on the parent's dataType is the most common version of this bug.
|
|
180
|
+
- **Correct pattern:** Resolve every media type from the parent **or** its children, consistently — `post.childrenPosts?.find(c => c.dataType === 'poll')?.getPollInfo()`, same for image/video/room/file. Process `post.children` / `.childrenPosts` / `getChildren()` for all of them.
|
|
181
|
+
- **Wrong pattern:** `Text(post.data.text)` with no children handling — or handling children for images only while checking `post.dataType === 'poll'/'video'/'room'` (the parent) for the rest.
|
|
182
|
+
|
|
183
|
+
### Render Every Post Type the Feed Returns
|
|
184
|
+
A global or community feed query without a `dataTypes` filter returns **all** post types — text, image, video, file, poll, livestream, and room. A renderer that branches on only one or two types silently renders the rest as blank cards. This is the single most common "the feed looks broken" complaint.
|
|
185
|
+
- **Correct pattern:** Resolve each media type from **both the parent and its `childrenPosts`** — in a feed the parent is usually `dataType: 'text'` and the image/video/poll/room rides on a *child* post (see Parent-Child Post Rendering). Use a helper per type, e.g. `post.dataType === 'poll' ? post.getPollInfo() : post.childrenPosts?.find(c => c.dataType === 'poll')?.getPollInfo()` — and the same for image/video/room/file. For polls, read each answer via `answer.data` (a plain string) and `answer.image?.fileUrl`, support `votePoll`/`unvotePoll`, and show `poll.closedAt`/`closedIn`. For room/livestream, display the entity's `title`/`status` — never the raw `roomId`. **And when you render the text body itself (post or comment): if `metadata` carries mention entries (`{ type: 'user', index, length, userId }`, `length` excluding the `@`), wrap each `[index, index + length + 1]` span in a styled element and resolve `userId` to a display name — don't print raw text and lose the @mention highlight.** (Pass `mentionees` on create so mentioned users are notified.)
|
|
186
|
+
- **Wrong pattern:** Gating media on the **parent's** dataType only — `const poll = post.dataType === 'poll' ? post.getPollInfo() : undefined` — while resolving images from children. The feed parent is `'text'`, so that branch never matches and polls/videos/rooms silently never render. Vise's `posts.parent-child-rendered` flags this inconsistency. Likewise, rendering `data.text` directly with no use of `metadata` mention offsets drops @mention highlights.
|
|
187
|
+
- Coverage of *which* types appear is **not** deterministically enforced (a feed may legitimately contain only some types) — it is your responsibility to match rendering to what the feed can return.
|
|
188
|
+
|
|
189
|
+
### Read SDK Objects Through Their Accessors, Not Casts
|
|
190
|
+
Casting an SDK return value to a hand-written shape — `(post.data as { text?: string }).text` — silences the type-checker. When an SDK upgrade renames a field, the build still compiles and the bug only appears at runtime.
|
|
191
|
+
- **Correct pattern:** Use the SDK's typed accessors and types (`post.getImageInfo()`, `post.getPollInfo()`, the `Amity.*` types) so `tsc`/`analyze` flags field-level breakage on upgrade.
|
|
192
|
+
- **Wrong pattern:** `as { … }` casts over `post.data`, `comment.data`, `answer.data`, or community/user fields.
|
|
193
|
+
|
|
194
|
+
### Comment Surfaces Need Read, Write, and an Explicit `pageSize`
|
|
195
|
+
A comment thread is incomplete if it only lists comments. Users expect to post.
|
|
196
|
+
- **Correct pattern:** Pair `CommentRepository.getComments({ …, pageSize: 20 }, cb)` (read) with a composer that calls `createComment` (write), gated behind the user's ban state. Always pass an explicit `pageSize` — the default pagination uses skip/limit, which the server rejects for scrollable queries with a runtime 500000 error. Render replies via `getComments({ parentId })` when `childrenNumber > 0`.
|
|
197
|
+
- **Wrong pattern:** A read-only comment list, or `getComments` with no `pageSize`.
|
|
171
198
|
|
|
172
199
|
### Explicit Feed Target Types
|
|
173
200
|
When calling `createPost`, do not hardcode the `targetType` to a literal value like `AmityPostTargetType.COMMUNITY` if the composer component is meant to be reusable.
|
|
@@ -196,43 +223,13 @@ Amity routes custom-shape post payloads via the `dataType` tag. Omitting it make
|
|
|
196
223
|
|
|
197
224
|
## SDK Version Handling
|
|
198
225
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
## Attestation Flow
|
|
202
|
-
|
|
203
|
-
`vise check` reports each applicable rule as one of `deterministic-pass`, `attestation-needed`, `failed`, or `blocked`. `attestation-needed` and `failed` are NOT warnings to skip — they block "I'm done." Resolve them in one of two ways:
|
|
204
|
-
|
|
205
|
-
1. **Make the deterministic check fire (preferred).** Re-read `vise explain <rule-id>` for the rule's enforcement details, adjust the code so the check passes, then run `vise check` again. After it goes green, run `vise sync` to persist the deterministic-pass attestation files.
|
|
206
|
-
2. **Record an attestation with evidence.** When the rule cannot fire because the customer's architecture is unconventional (DI wrappers, custom abstractions, etc.), use `vise attest`:
|
|
207
|
-
|
|
208
|
-
```bash
|
|
209
|
-
vise attest . \
|
|
210
|
-
--rule typescript.client.region \
|
|
211
|
-
--confidence high \
|
|
212
|
-
--signer host-agent \
|
|
213
|
-
--rationale "API_REGIONS.EU is explicitly passed as the second argument to Client.createClient in src/socialSDK.ts." \
|
|
214
|
-
--evidence-file ./.tmp/evidence.json
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
The evidence file is a JSON object matching the `evidence_required` schema for that rule (`vise explain <rule-id>` shows the required fields). Include concrete source file paths in evidence values whenever the evidence depends on code, for example `{ "region_source": "src/socialSDK.ts" }` or `{ "login_flow": "src/auth/socialSession.ts" }`. `vise attest` fingerprints cited files, and future `vise check` runs mark the attestation stale if those files change.
|
|
218
|
-
|
|
219
|
-
### Confidence honesty
|
|
220
|
-
|
|
221
|
-
- `high` — direct evidence the deterministic check would have fired if it had been more flexible. Auto-recorded.
|
|
222
|
-
- `medium` — reasoned about it but cannot point to a single unambiguous location. May require human signer depending on rule.
|
|
223
|
-
- `low` — guess. Always requires `--signer human`.
|
|
224
|
-
|
|
225
|
-
If the rule's `escalation_threshold` is above your confidence, `vise attest --signer host-agent` is rejected. In v0.4 there is no hosted review queue yet, so the path is: stop, surface to the user with the rule id and the evidence you have, and ask them to run `vise attest --signer human --identity <their name or email>` themselves.
|
|
226
|
-
|
|
227
|
-
### What `vise sync` is NOT
|
|
226
|
+
For a **new (greenfield) install**, resolve the **current latest** SDK at install time — never copy a version number from examples, docs snippets, or memory, which go stale (e.g. an old major can lack `StoryRepository`). Install latest, then pin the resolved version: `npm install @amityco/ts-sdk@latest` (TypeScript/React Native; the lockfile captures the exact version), or the latest Maven / CocoaPods·SwiftPM / pub.dev release for Android / iOS / Flutter. `vise plan` surfaces an `sdkVersion` field — for TypeScript/React Native it reports the current npm `latest`; **heed it**.
|
|
228
227
|
|
|
229
|
-
- `vise
|
|
230
|
-
- Passing unknown flags to `vise sync` will error in v0.4 — don't try to use it as a shortcut for attestation.
|
|
231
|
-
- To record an attestation, use `vise attest`. Always.
|
|
228
|
+
Do not use floating SDK dependency versions such as `latest`, `*`, `any`, `+`, or branch-based iOS package references in the committed manifest. Pin the social.plus SDK to an explicit version or reviewed semver range, then let the host app's package manager lockfile capture the exact resolved version. If `vise plan` reports that an **existing** project is on an older major than current latest, treat that as an FYI only — a major upgrade is out of scope unless the task explicitly asks for it.
|
|
232
229
|
|
|
233
|
-
|
|
230
|
+
## Resolving `vise check` Findings
|
|
234
231
|
|
|
235
|
-
|
|
232
|
+
`vise check` reports each rule as `deterministic-pass`, `attestation-needed`, `failed`, or `blocked`. **`attestation-needed` and `failed` block "I'm done" — they are not skippable warnings.** Resolve each one of two ways: (1) **preferred** — adjust the code so the deterministic check fires (`vise explain <rule-id>` shows what it wants), then `vise check` green → `vise sync`; or (2) record `vise attest` with evidence when the architecture is genuinely unconventional. Confidence honesty, the `vise attest` evidence-file format, escalation/human-signer rules, and "`vise sync` is not attestation": **see `reference/operations.md`**.
|
|
236
233
|
|
|
237
234
|
## Feature Guidance
|
|
238
235
|
|
|
@@ -245,6 +242,14 @@ vise plan . --request "<feed or post request>"
|
|
|
245
242
|
|
|
246
243
|
Require a concrete target from the app: current user feed, selected community, selected channel, or another user-provided domain object. Do not hardcode random target IDs.
|
|
247
244
|
|
|
245
|
+
**Decide engagement scope explicitly — Vise authors the checklist, you subtract with a reason.** `vise plan` returns a `completenessChecklist` (the canonical capabilities for the outcome, e.g. comments, reactions, pagination, polls, media, moderation) and `vise check` reports each as present / missing / opted-out. This is **advisory — it never fails the check**; it exists so completeness doesn't depend on your memory. For each capability: build it, or explicitly opt out with a recorded marker in the code so the omission is reviewable, not accidental:
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
// vise: scope-omit poll — text + image feed only; polls disabled for this tenant
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Do not silently drop a capability. `vise check`'s `completeness` section will keep nudging on anything that's neither built nor opted-out.
|
|
252
|
+
|
|
248
253
|
**Demo wiring (when the app needs to compile before the real target source exists):** even at the top-level `App` / `MaterialApp` / `_app.tsx` / `AppDelegate` wiring site, do not pass a literal string. Use the host platform's compile-time env channel so the rule sees no string literal at the call site:
|
|
249
254
|
|
|
250
255
|
- Flutter: `const String.fromEnvironment('AMITY_COMMUNITY_ID')` — value injected via `flutter run --dart-define=AMITY_COMMUNITY_ID=…`
|
|
@@ -293,6 +298,11 @@ When rendering moderation actions (like `flagPost`, `deletePost`, `banUser`, `mu
|
|
|
293
298
|
|
|
294
299
|
Similarly, moderator-only data such as `flagCount` must not be leaked to non-moderators. Rendering `post.flagCount` unconditionally exposes moderation signal to all users, which is a privacy regression. Gating it with a role check ensures it is only visible to authorized users.
|
|
295
300
|
|
|
301
|
+
**Post/comment actions are gated by the viewer's relationship to the content**, and ownership is distinct from moderator role:
|
|
302
|
+
- **Author** (`post.postedUserId === currentUserId`): show edit (`editPost`/`updateComment`) and delete (`deletePost`/`deleteComment`).
|
|
303
|
+
- **Non-author**: show report/flag (`flagPost`/`flagComment`).
|
|
304
|
+
- A post card with no actions menu at all is incomplete. Do not show edit/delete to non-authors, and do not rely on the server's 403 to hide them.
|
|
305
|
+
|
|
296
306
|
### User Ban State
|
|
297
307
|
|
|
298
308
|
Banned users still see interaction buttons (like `createPost`, `createComment`, `sendMessage`) if you don't check their ban state before rendering them. These API calls will fail on the server, causing a poor UX and support tickets.
|
|
@@ -324,16 +334,49 @@ Require chat shape (1:1, group, community), channel source (never hardcode chann
|
|
|
324
334
|
- Use `community` or `live` for groups.
|
|
325
335
|
If the type intentionally mismatches the UI name, add `// vise: channel type rationale — <reason>` to the code.
|
|
326
336
|
|
|
327
|
-
|
|
337
|
+
For communities:
|
|
328
338
|
|
|
329
|
-
|
|
339
|
+
```bash
|
|
340
|
+
vise search-docs "community create join leave members invitation"
|
|
341
|
+
vise plan . --request "<community request>"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Require the community surface (create, browse/join, detail + members, or membership management), the privacy model (public instant-join vs private join-request approval — they differ), and a concrete community ID source (never hardcode a communityId). Query communities and members through a Live Collection, handle the join-request flow for private communities, gate moderator actions (roles/ban) by role, and clean up observers. `vise plan` returns the community `completenessChecklist`; opt out of anything out of scope with `// vise: scope-omit <id> <reason>`.
|
|
345
|
+
|
|
346
|
+
For the social graph (follow/followers):
|
|
330
347
|
|
|
331
348
|
```bash
|
|
332
|
-
vise
|
|
333
|
-
vise
|
|
334
|
-
vise run-sensors . --surface apps/web
|
|
349
|
+
vise search-docs "follow unfollow followers following block user"
|
|
350
|
+
vise plan . --request "<follow/social-graph request>"
|
|
335
351
|
```
|
|
336
352
|
|
|
353
|
+
Require the surface (follow button, follower list, following list, or blocked users), the follow model (automatic vs follow-request approval — they differ), and a concrete target userId source (never invent a userId). Query follower/following lists through a Live Collection; handle the follow-request pending/accept/decline flow when following isn't automatic; clean up observers.
|
|
354
|
+
|
|
355
|
+
For an in-app notification tray (distinct from push setup — that's a push/FCM/APNS concern):
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
vise search-docs "in-app notification tray seen settings"
|
|
359
|
+
vise plan . --request "<notification tray request>"
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Observe the notification tray as a Live Object/Collection, mark items/tray seen so the unseen badge clears server-side, respect Amity's server-side notification settings, and clean up the observer.
|
|
363
|
+
|
|
364
|
+
## Design-Conformant UI (Design Contract)
|
|
365
|
+
|
|
366
|
+
The customer wants social.plus UI that looks like *their* app, not a generic kit. Match their aesthetic from whatever design signal exists, in this priority:
|
|
367
|
+
|
|
368
|
+
1. **Customer-provided prototype (HTML/CSS).** If the customer gives you an HTML/CSS prototype (a static mockup, an existing page, a design export), run `vise design extract <prototypePath> --repo .` first. It writes `sp-vise/design-contract.json` — a *graded* contract: **declared** tokens (CSS custom properties — exact, authoritative) and **inferred** tokens (repeated literal values — advisory). `vise plan` then surfaces this contract in its `designContract` field. Build the feed using the declared tokens; treat inferred tokens as advisory and confirm brand values with the customer when the contract `strength` is `weak`.
|
|
369
|
+
2. **Host app's own design system.** With no external prototype, run `vise design extract --from-project .` to derive the contract from the project's *own* design system — CSS custom properties (incl. shadcn `:root` and Tailwind v4 `@theme`), TS/JS token modules (`theme.ts`/`tokens.ts`), inline tailwind configs, and native sources (Android `colors.xml`/`dimens.xml`, Flutter `Color(0x…)`, iOS `.xcassets/*.colorset` + Swift colors). Then build the new surface from those tokens (also enforced by `*.design.reuse-detected-tokens`). `vise inspect` lists the detected design signals.
|
|
370
|
+
3. **Neither.** Use a neutral, accessible default and **state in your summary that no design source was found** — do not invent brand colors, spacing, or a design system. Ask the customer for a prototype, their theme file, or screenshots.
|
|
371
|
+
|
|
372
|
+
To let the customer (or yourself) **see** the contract and check the match, run `vise design preview .` — it writes a self-contained `sp-vise/design-preview.html` with the tokens as visual swatches, the conformance report, and the HTML prototype embedded (`--reference <prototype>`) for side-by-side review. It's advisory: it renders the artifact for a human/VLM to judge, and is not an automated pixel diff (a true render diff belongs to your own browser tooling, not Vise).
|
|
373
|
+
|
|
374
|
+
The design contract is **advisory generation guidance**: it shapes what you build, but it is not a deterministic gate and never fails `vise check`. A token-poor prototype yields a weaker — never wrong — contract. When you record a design attestation, cite the contract `digest` so the feed can be claimed conformant to the prototype.
|
|
375
|
+
|
|
376
|
+
## Monorepos
|
|
377
|
+
|
|
378
|
+
If `vise inspect .` reports multiple surfaces, pick the intended app and pass `--surface <path>` consistently to `plan`/`validate`/`run-sensors`. Details: `reference/operations.md`.
|
|
379
|
+
|
|
337
380
|
## Final Response Discipline
|
|
338
381
|
|
|
339
382
|
Report:
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Debugging & Troubleshooting (Day 2 Operations)
|
|
2
|
+
|
|
3
|
+
Load this when the customer reports an SDK-specific runtime crash, build failure, or logical error (e.g. data not loading).
|
|
4
|
+
|
|
5
|
+
Start with `vise debug` before broad repo inspection or sidecar exploration.
|
|
6
|
+
|
|
7
|
+
**CRITICAL SCOPE LIMIT:** Do NOT spam `vise debug` for generic UI/CSS errors or non-SDK issues. ONLY call `vise debug` if the error explicitly contains keywords like `social.plus`, `social-plus`, `Amity`, `live-collection`, or if the error occurs in a file that imports `social.plus` packages.
|
|
8
|
+
|
|
9
|
+
For the first repair pass, prefer the compact troubleshooting loop:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
vise debug . --error-file "logs/crash.log" --brief
|
|
13
|
+
vise check . --ci
|
|
14
|
+
vise run-sensors .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
When debugging, pass the stack trace to Vise:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
vise debug . --error "stack trace or exception message here"
|
|
21
|
+
```
|
|
22
|
+
Or, if the error is in a file:
|
|
23
|
+
```bash
|
|
24
|
+
vise debug . --error-file "logs/crash.log"
|
|
25
|
+
```
|
|
26
|
+
Or, for a compact repair brief:
|
|
27
|
+
```bash
|
|
28
|
+
vise debug . --error-file "logs/crash.log" --brief
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The output contains `likelyCause`, `correlatedRules`, and a compact `repairBrief` with:
|
|
32
|
+
|
|
33
|
+
- the primary rule to repair first
|
|
34
|
+
- likely file hints
|
|
35
|
+
- minimum patch shape
|
|
36
|
+
- invariants to preserve
|
|
37
|
+
- verification commands
|
|
38
|
+
|
|
39
|
+
Do not guess the fix before consulting `vise debug`. Do not read broad `sp-vise/` history on the first pass unless the `repairBrief` is ambiguous or the first repair fails.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Operations: Attestation, Engagement Scope, Monorepos
|
|
2
|
+
|
|
3
|
+
Load this when resolving `vise check` findings, working with engagement scope, or operating in a monorepo.
|
|
4
|
+
|
|
5
|
+
## Attestation Flow
|
|
6
|
+
|
|
7
|
+
`vise check` reports each applicable rule as one of `deterministic-pass`, `attestation-needed`, `failed`, or `blocked`. `attestation-needed` and `failed` are NOT warnings to skip — they block "I'm done." Resolve them in one of two ways:
|
|
8
|
+
|
|
9
|
+
1. **Make the deterministic check fire (preferred).** Re-read `vise explain <rule-id>` for the rule's enforcement details, adjust the code so the check passes, then run `vise check` again. After it goes green, run `vise sync` to persist the deterministic-pass attestation files.
|
|
10
|
+
2. **Record an attestation with evidence.** When the rule cannot fire because the customer's architecture is unconventional (DI wrappers, custom abstractions, etc.), use `vise attest`:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
vise attest . \
|
|
14
|
+
--rule typescript.client.region \
|
|
15
|
+
--confidence high \
|
|
16
|
+
--signer host-agent \
|
|
17
|
+
--rationale "API_REGIONS.EU is explicitly passed as the second argument to Client.createClient in src/socialSDK.ts." \
|
|
18
|
+
--evidence-file ./.tmp/evidence.json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The evidence file is a JSON object matching the `evidence_required` schema for that rule (`vise explain <rule-id>` shows the required fields). Include concrete source file paths in evidence values whenever the evidence depends on code, for example `{ "region_source": "src/socialSDK.ts" }` or `{ "login_flow": "src/auth/socialSession.ts" }`. `vise attest` fingerprints cited files, and future `vise check` runs mark the attestation stale if those files change.
|
|
22
|
+
|
|
23
|
+
### Confidence honesty
|
|
24
|
+
|
|
25
|
+
- `high` — direct evidence the deterministic check would have fired if it had been more flexible. Auto-recorded.
|
|
26
|
+
- `medium` — reasoned about it but cannot point to a single unambiguous location. May require human signer depending on rule.
|
|
27
|
+
- `low` — guess. Always requires `--signer human`.
|
|
28
|
+
|
|
29
|
+
If the rule's `escalation_threshold` is above your confidence, `vise attest --signer host-agent` is rejected. In v0.4 there is no hosted review queue yet, so the path is: stop, surface to the user with the rule id and the evidence you have, and ask them to run `vise attest --signer human --identity <their name or email>` themselves.
|
|
30
|
+
|
|
31
|
+
### What `vise sync` is NOT
|
|
32
|
+
|
|
33
|
+
- `vise sync` does **not** accept `--attest` or any flag. It only persists deterministic-pass results.
|
|
34
|
+
- Passing unknown flags to `vise sync` will error in v0.4 — don't try to use it as a shortcut for attestation.
|
|
35
|
+
- To record an attestation, use `vise attest`. Always.
|
|
36
|
+
|
|
37
|
+
### Treat warnings as blockers
|
|
38
|
+
|
|
39
|
+
If a legacy `vise validate` run reports `needs-review` with warnings, those warnings correspond to compliance rules in `attestation-needed` state. Do not interpret them as "code is fine, just static-analysis noise." Run `vise check` to get the structured rule list, then resolve each one as above.
|
|
40
|
+
|
|
41
|
+
## Engagement Scope (Optional)
|
|
42
|
+
|
|
43
|
+
If the customer has set up `sp-vise/engagement.json` (via `vise engagement init`), respect the declared scope. `vise engagement show .` prints the engagement metadata: tier, customer id, contracted outcomes, reviewer assignment.
|
|
44
|
+
|
|
45
|
+
When `vise init` reports a `warnings` array containing "not in the engagement scope", surface that to the user — don't silently widen scope. The user either extends the scope by editing `engagement.json` or accepts that the current work is out-of-contract.
|
|
46
|
+
|
|
47
|
+
If no `engagement.json` exists, do not create one without the user's intent. `vise engagement init` is a contractual artifact; agents should only run it when the user explicitly asks to set up engagement metadata, typically with `--tier`, `--customer-id`, and `--scope`.
|
|
48
|
+
|
|
49
|
+
If `vise` is not installed, use `npm install -g @amityco/social-plus-vise`, then retry the `vise` command.
|
|
50
|
+
|
|
51
|
+
## Monorepos
|
|
52
|
+
|
|
53
|
+
If `vise inspect .` reports multiple surfaces, choose the intended app with the user or surrounding task context. Pass it consistently:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
vise plan . --surface apps/web --request "<user intent>"
|
|
57
|
+
vise validate . --surface apps/web
|
|
58
|
+
vise run-sensors . --surface apps/web
|
|
59
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vise-harness-engineer
|
|
3
|
+
description: "Instructions for authoring new compliance rules in social.plus Vise."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Vise Harness Engineer Instructions
|
|
7
|
+
|
|
8
|
+
When asked by the user to add a new compliance rule (e.g., "Add a rule for post impressions" or "Integrators misconfigure X"), follow these steps:
|
|
9
|
+
|
|
10
|
+
## 1. Context Gathering
|
|
11
|
+
You MUST verify that the feature exists and understand its implementation before authoring a rule.
|
|
12
|
+
- Read `social-plus-docs/llms.txt` to verify feature support and understand its SDK surface.
|
|
13
|
+
- Inspect the relevant SDK codebase (e.g., `AmityTypescriptSDK`) to see how the feature is implemented.
|
|
14
|
+
- Check `social-plus-foundry/rules/` to find the correct YAML file to modify (e.g., `feed.yaml` or `auth.yaml`).
|
|
15
|
+
|
|
16
|
+
## 2. Rule Authoring
|
|
17
|
+
Add a new YAML block to the appropriate rule file following this schema:
|
|
18
|
+
- `id`: A unique string identifying the rule (e.g., `react.feed.impressions`).
|
|
19
|
+
- `title`: A human-readable title.
|
|
20
|
+
- `severity`: `warning` or `error`.
|
|
21
|
+
- `rationale`: Why this rule exists.
|
|
22
|
+
- `feedforward`: IMPORTANT! Proactive instructions that will be injected into the integrator's plan to prevent the bug. Use strong language like "You MUST...".
|
|
23
|
+
- `enforcement.inferential`: Provide a precise prompt for the integrator's host agent to review the code.
|
|
24
|
+
```yaml
|
|
25
|
+
enforcement:
|
|
26
|
+
inferential:
|
|
27
|
+
prompt: "Review the Feed component. Does it use IntersectionObserver to fire trackImpression?"
|
|
28
|
+
target_signer: "host-agent"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 3. Do Not Create deterministic Sensors Unless Trivial
|
|
32
|
+
Do not write complex AST deterministic sensors (`validator-finding-absent`) unless explicitly requested. The inferential sensor is the preferred fallback for complex logic.
|
|
33
|
+
|
|
34
|
+
## 4. No capabilities.yaml Modifications
|
|
35
|
+
The capability matrix is fetched dynamically. Do not attempt to update any `capabilities.yaml` file.
|
|
Binary file
|