|
19 | 19 | {"id":"hypercerts-sdk-bub.1","title":"Use isValidUri in imageToUrl to pass through all valid URI schemes instead of only http(s)","description":"## Files\n- packages/sdk-core/src/repository/ProfileOperationsImpl.ts (modify)\n- packages/sdk-core/tests/repository/ProfileOperationsImpl.test.ts (modify)\n\n## What to do\n\n### In ProfileOperationsImpl.ts\n\n1. Add import for `isValidUri` from `../lib/url-utils.js` (line ~12 area, alongside existing imports)\n\n2. Replace the current scheme check in `imageToUrl` (lines 111-113):\n ```typescript\n // BEFORE (remove this):\n if (result.startsWith(\"http://\") || result.startsWith(\"https://\")) {\n return result;\n }\n\n // AFTER (replace with):\n if (isValidUri(result)) {\n return result;\n }\n ```\n\n This reuses the existing `isValidUri` function from `src/lib/url-utils.ts` which matches any valid URI scheme per RFC 3986 (uses regex `/^[a-zA-Z][a-zA-Z0-9+\\-.]*:/` and the native URL constructor). It already handles http, https, ipfs, at, data, and any other valid scheme.\n\n3. Keep everything else in `imageToUrl` unchanged:\n - The `extractCidFromImage(image)` call\n - The falsy check that throws `Error(\"Unable to extract CID or URI from image record\")`\n - The fallthrough to `getBlobUrl(this.pdsUrl, this.repoDid, result)` for bare CIDs\n\n### In ProfileOperationsImpl.test.ts\n\n4. Add a test case in the `getCertifiedProfile` describe block (after the existing \"should handle URI image format\" test around line 152) that verifies non-http URI schemes are passed through unchanged:\n\n ```typescript\n it(\"should pass through non-http URI schemes unchanged (e.g., ipfs://)\", async () =\u003e {\n mockAgent.getProfile!.mockResolvedValue({\n success: true,\n data: { handle: \"test.bsky.social\" },\n });\n\n mockAgent.com.atproto.repo.getRecord.mockResolvedValue({\n success: true,\n data: {\n value: {\n $type: CERTIFIED_PROFILE_COLLECTION,\n createdAt: \"2024-01-01T00:00:00.000Z\",\n displayName: \"Test\",\n avatar: {\n $type: \"org.hypercerts.defs#uri\",\n uri: \"ipfs://QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG\",\n },\n banner: {\n $type: \"org.hypercerts.defs#uri\",\n uri: \"ar://some-arweave-tx-id\",\n },\n },\n },\n });\n\n const result = await profileOps.getCertifiedProfile();\n expect(result).not.toBeNull();\n expect(result!.avatar).toBe(\"ipfs://QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG\");\n expect(result!.banner).toBe(\"ar://some-arweave-tx-id\");\n });\n ```\n\n## Dont\n- Do NOT change `extractCidFromImage` or `getBlobUrl` — those are correct as-is\n- Do NOT change the error message or error type for the falsy check\n- Do NOT add a new URI validation function — reuse `isValidUri` from `../lib/url-utils.js`\n- Do NOT change any other method in ProfileOperationsImpl\n- Do NOT modify the `isValidUri` function itself","acceptance_criteria":"1. `pnpm --filter @hypercerts-org/sdk-core test ProfileOperationsImpl` passes — all existing tests still pass\n2. The new test \"should pass through non-http URI schemes unchanged\" passes\n3. `pnpm --filter @hypercerts-org/sdk-core build` succeeds\n4. `pnpm --filter @hypercerts-org/sdk-core lint` passes\n5. In the source, `imageToUrl` calls `isValidUri(result)` instead of checking `startsWith(\"http\")` / `startsWith(\"https\")`\n6. `isValidUri` is imported from `../lib/url-utils.js`\n7. No new utility functions were created","status":"closed","priority":2,"issue_type":"task","assignee":"kzoeps","owner":"kzoepa@gmail.com","estimated_minutes":30,"created_at":"2026-02-16T12:35:54.50661348+06:00","created_by":"kzoeps","updated_at":"2026-02-16T14:25:22.123150145+06:00","closed_at":"2026-02-16T14:25:22.123150145+06:00","close_reason":"9eb7015 Use isValidUri to pass through all valid URI schemes in imageToUrl","labels":["scope:trivial"],"dependencies":[{"issue_id":"hypercerts-sdk-bub.1","depends_on_id":"hypercerts-sdk-bub","type":"parent-child","created_at":"2026-02-16T12:35:54.507982699+06:00","created_by":"kzoeps"}]} |
20 | 20 | {"id":"hypercerts-sdk-c13","title":"Add JSDoc documentation for collection avatar/banner","description":"Add JSDoc documentation to createCollection() and updateCollection() methods about avatar field. Add usage examples in method documentation showing avatar in collections and projects.","notes":"## Source\nFrom: specs/lexicon-sync/v0.10.0-beta.4-v0.10.0-beta.11.md (Change 3)\nCHANGELOG: PR #106 - Add avatar and banner fields to collection lexicon\n\n## What Changed\n- Collections now have avatar and banner fields for visual representation\n- These are image fields (Uri, SmallImage, or LargeImage)\n\n## Implementation Status\n- [x] Verify CreateCollectionParams supports avatar/banner\n- [x] Verify createCollection/updateCollection handle fields\n- [ ] Add JSDoc documentation about avatar (THIS TASK)\n- [ ] Add usage examples for avatar in collections and projects\n- [x] Tests complete\n\n## Validation Checklist\n- [ ] Format check passes (pnpm format:check)\n- [ ] Lint passes (pnpm lint)\n- [ ] Typecheck passes (pnpm typecheck)\n- [ ] Build passes (pnpm build)\n- [ ] Tests pass (pnpm test)\n\n## Changeset\n- Type: minor (new feature available)\n- Already created\n\nNote: Method documentation mentions banner but not avatar\n\nIMPORTANT: Keep this issue in sync with the source .md file.","status":"closed","priority":2,"issue_type":"task","owner":"adam@hypercerts.org","created_at":"2026-01-27T16:47:16.053509722+13:00","created_by":"Adam Spiers","updated_at":"2026-01-28T10:52:01.615872728+13:00","closed_at":"2026-01-28T10:52:01.615872728+13:00","close_reason":"Added comprehensive JSDoc documentation with avatar/banner examples to createCollection, updateCollection, createProject, and updateProject methods"} |
21 | 21 | {"id":"hypercerts-sdk-d2e","title":"Add tests for location string format","description":"Add explicit tests verifying location string format works (e.g., 'New York, NY, USA' as location value).","notes":"Follow-up from hypercerts-sdk-m7h (beta.13 location string format support).\n\nThe implementation already works - this adds explicit test coverage.\n\nTest cases to add in packages/sdk-core/tests/repository/HypercertOperationsImpl.test.ts:\n- createLocationRecord with string location value\n- attachLocation with inline string location\n- Verify string gets wrapped in URI ref format","status":"closed","priority":3,"issue_type":"task","owner":"adam@hypercerts.org","created_at":"2026-01-29T14:38:14.006311332+13:00","created_by":"Adam Spiers","updated_at":"2026-01-29T15:18:11.946123637+13:00","closed_at":"2026-01-29T15:18:11.946123637+13:00","close_reason":"Added tests for simple text string location format in attachLocation and createCollection"} |
22 | | -{"id":"hypercerts-sdk-dii","title":"Epic: Use configured scope in loopback client_id instead of hardcoding","description":"The loopback OAuth client_id builder in OAuthClient.buildClientId() currently hardcodes 'atproto transition:generic' as the scope query parameter. Per AT Protocol spec, loopback clients embed scope in the client_id URL — but the scope should come from the user's config (this.config.oauth.scope), not be hardcoded. This creates a mismatch when users configure a different scope: the client_id URL says one thing, but the authorize() call and metadata body may say another. The fix is to pass the configured scope through to the URL query parameter, just like redirect_uri is already passed through. Success: the scope embedded in the loopback client_id URL always matches config.oauth.scope.","status":"open","priority":1,"issue_type":"epic","owner":"karma.gainforest.id","created_at":"2026-02-20T19:38:48.559721302+06:00","created_by":"karma.gainforest.id","updated_at":"2026-02-20T19:38:48.559721302+06:00","labels":["scope:small"]} |
| 22 | +{"id":"hypercerts-sdk-dii","title":"Epic: Use configured scope in loopback client_id instead of hardcoding","description":"The loopback OAuth client_id builder in OAuthClient.buildClientId() currently hardcodes 'atproto transition:generic' as the scope query parameter. Per AT Protocol spec, loopback clients embed scope in the client_id URL — but the scope should come from the user's config (this.config.oauth.scope), not be hardcoded. This creates a mismatch when users configure a different scope: the client_id URL says one thing, but the authorize() call and metadata body may say another. The fix is to pass the configured scope through to the URL query parameter, just like redirect_uri is already passed through. Success: the scope embedded in the loopback client_id URL always matches config.oauth.scope.","status":"closed","priority":1,"issue_type":"epic","assignee":"karma.gainforest.id","owner":"karma.gainforest.id","created_at":"2026-02-20T19:38:48.559721302+06:00","created_by":"karma.gainforest.id","updated_at":"2026-02-20T19:45:12.627845593+06:00","closed_at":"2026-02-20T19:45:12.627850733+06:00","labels":["scope:small"]} |
23 | 23 | {"id":"hypercerts-sdk-dii.1","title":"Use config scope in buildClientId instead of hardcoded value","description":"## Files\n- packages/sdk-core/src/auth/OAuthClient.ts (modify)\n\n## What to do\nIn `buildClientId()` (line 181):\n1. Rename `_scope` parameter back to `scope` (remove the underscore prefix)\n2. Change line 189 from:\n `loopbackUrl.searchParams.set(\"scope\", \"atproto transition:generic\");`\n to:\n `loopbackUrl.searchParams.set(\"scope\", scope);`\n3. Update the info log message on line 194-195 to be dynamic instead of hardcoding \"atproto transition:generic\". Change it to something like:\n `Development mode: using loopback client_id http://localhost with scope \"${scope}\"`\n And include `scope` in the log context object.\n4. Update the JSDoc for the `_scope` parameter (line 176) — remove \"(ignored - always uses \\\"atproto transition:generic\\\")\" and replace with \"The OAuth scope to embed in the loopback client_id URL\"\n\n## Dont\n- Do NOT change the `buildClientMetadata()` method — it already correctly uses `this.config.oauth.scope` for loopback via `metadataScope` on line 241\n- Do NOT change the `authorize()` method\n- Do NOT change any other files in this task","acceptance_criteria":"1. `buildClientId()` uses the `scope` parameter (not `_scope`) to set the scope query param in the loopback URL\n2. The hardcoded string \"atproto transition:generic\" no longer appears in `buildClientId()`\n3. The info log message dynamically includes the actual scope value\n4. `pnpm --filter @hypercerts-org/sdk-core build` succeeds\n5. `pnpm --filter @hypercerts-org/sdk-core test` passes (some tests will need updating — see sibling task)","status":"closed","priority":1,"issue_type":"task","assignee":"karma.gainforest.id","owner":"karma.gainforest.id","estimated_minutes":20,"created_at":"2026-02-20T19:39:02.942742664+06:00","created_by":"karma.gainforest.id","updated_at":"2026-02-20T19:39:52.104278836+06:00","closed_at":"2026-02-20T19:39:52.10428415+06:00","labels":["scope:trivial"],"dependencies":[{"issue_id":"hypercerts-sdk-dii.1","depends_on_id":"hypercerts-sdk-dii","type":"parent-child","created_at":"2026-02-20T19:39:02.944601131+06:00","created_by":"karma.gainforest.id"}]} |
24 | 24 | {"id":"hypercerts-sdk-dii.2","title":"Update loopback tests to expect config scope instead of hardcoded scope","description":"## Files\n- packages/sdk-core/tests/auth/OAuthClient.test.ts (modify)\n\n## What to do\nUpdate the loopback test suite (`describe(\"loopback client_id auto-generation\")`) to reflect that `buildClientId()` now uses the configured scope instead of hardcoding \"atproto transition:generic\".\n\nSpecific changes:\n\n1. **Test \"should always use atproto transition:generic scope\" (line 501)**: This test is now wrong. Rename it to something like \"should embed configured scope in loopback client_id\". The test configures `scope: \"atproto\"` but then asserts the log mentions `\"atproto transition:generic\"`. After the fix, the log should mention the configured scope (`\"atproto\"`). Update the assertion on line 528 to check for the configured scope value instead.\n\n2. **Test \"should auto-generate for http://localhost\" (line 296)**: The log message assertion on line 323 checks for \"Development mode\" which is fine. No change needed unless the log message format changed.\n\n3. **Add a new test**: \"should embed custom scope in loopback client_id URL\" — configure with `scope: \"atproto transition:generic transition:email\"`, create the client, trigger initialization, and verify the info log message includes that exact scope string.\n\n4. **Add a new test**: \"should embed scope from config, not hardcoded value\" — configure with `scope: \"atproto repo:*\"`, create the client, trigger initialization, and verify the log does NOT contain \"transition:generic\" and DOES contain \"atproto repo:*\".\n\n## Dont\n- Do NOT modify any source files (only test files)\n- Do NOT remove existing tests that are still valid (the rewrite warning tests, non-loopback test, etc. are all still correct)\n- Do NOT change the test structure/describe blocks","acceptance_criteria":"1. The test \"should always use atproto transition:generic scope\" is renamed and updated to verify the configured scope is used\n2. At least one new test verifies a non-default scope (e.g. \"atproto repo:*\") is embedded in the loopback client_id\n3. All tests in `pnpm --filter @hypercerts-org/sdk-core test` pass\n4. No test asserts that \"atproto transition:generic\" is always hardcoded in the loopback URL","status":"closed","priority":1,"issue_type":"task","assignee":"karma.gainforest.id","owner":"karma.gainforest.id","estimated_minutes":30,"created_at":"2026-02-20T19:39:21.312332482+06:00","created_by":"karma.gainforest.id","updated_at":"2026-02-20T19:43:45.680973267+06:00","closed_at":"2026-02-20T19:43:45.680973267+06:00","close_reason":"d886d1a update loopback tests to verify configured scope in client_id","labels":["scope:small"],"dependencies":[{"issue_id":"hypercerts-sdk-dii.2","depends_on_id":"hypercerts-sdk-dii","type":"parent-child","created_at":"2026-02-20T19:39:21.31369194+06:00","created_by":"karma.gainforest.id"},{"issue_id":"hypercerts-sdk-dii.2","depends_on_id":"hypercerts-sdk-dii.1","type":"blocks","created_at":"2026-02-20T19:39:21.31574376+06:00","created_by":"karma.gainforest.id"},{"issue_id":"hypercerts-sdk-dii.2","depends_on_id":"hypercerts-sdk-dii.4","type":"blocks","created_at":"2026-02-20T19:40:12.355254192+06:00","created_by":"karma.gainforest.id"}]} |
25 | 25 | {"id":"hypercerts-sdk-dii.3","title":"Remove hardcoded scope override in buildClientMetadata for loopback","description":"## Files\n- packages/sdk-core/src/auth/OAuthClient.ts (modify)\n\n## What to do\nIn `buildClientMetadata()` (line 231), line 241 currently reads:\n```ts\nconst metadataScope = isLoopback ? \"atproto transition:generic\" : this.config.oauth.scope;\n```\n\nThis has the same hardcoding problem as `buildClientId()`. The loopback branch overrides the user's configured scope with \"atproto transition:generic\".\n\nChange line 241 to simply use the config scope for all cases:\n```ts\nconst metadataScope = this.config.oauth.scope;\n```\n\nOr remove the `metadataScope` variable entirely and use `this.config.oauth.scope` directly on line 257 where it is assigned to `scope: metadataScope`.\n\nEither approach is fine — the key point is that the metadata body scope must match the config scope, which will now also match the scope embedded in the loopback client_id URL (after the sibling task is done).\n\n## Dont\n- Do NOT change `buildClientId()` (that is a separate task)\n- Do NOT change `authorize()` or `validateClientMetadataScope()`\n- Do NOT change test files in this task","acceptance_criteria":"1. Line 241 no longer hardcodes \"atproto transition:generic\" for loopback clients\n2. The metadata scope always uses `this.config.oauth.scope` regardless of loopback vs non-loopback\n3. `pnpm --filter @hypercerts-org/sdk-core build` succeeds","status":"closed","priority":1,"issue_type":"task","assignee":"karma.gainforest.id","owner":"karma.gainforest.id","estimated_minutes":10,"created_at":"2026-02-20T19:39:40.249158586+06:00","created_by":"karma.gainforest.id","updated_at":"2026-02-20T19:39:52.163858806+06:00","closed_at":"2026-02-20T19:39:52.163865039+06:00","labels":["scope:trivial"],"dependencies":[{"issue_id":"hypercerts-sdk-dii.3","depends_on_id":"hypercerts-sdk-dii","type":"parent-child","created_at":"2026-02-20T19:39:40.250479571+06:00","created_by":"karma.gainforest.id"}]} |
|
0 commit comments