{"id":19967604,"url":"https://github.com/ceramicstudio/software-trust-workshop","last_synced_at":"2026-06-09T03:03:15.625Z","repository":{"id":229047670,"uuid":"775564629","full_name":"ceramicstudio/software-trust-workshop","owner":"ceramicstudio","description":"Prepared for a working session for SPD design","archived":false,"fork":false,"pushed_at":"2024-05-02T18:08:31.000Z","size":17655,"stargazers_count":1,"open_issues_count":4,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-01T17:47:47.512Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ceramicstudio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-21T16:13:36.000Z","updated_at":"2024-03-22T14:04:36.000Z","dependencies_parsed_at":"2024-04-15T20:29:35.644Z","dependency_job_id":"80fd7cfa-b1b4-4504-9ddf-dd5cbc0ed060","html_url":"https://github.com/ceramicstudio/software-trust-workshop","commit_stats":null,"previous_names":["ceramicstudio/software-trust-workshop"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ceramicstudio/software-trust-workshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicstudio%2Fsoftware-trust-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicstudio%2Fsoftware-trust-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicstudio%2Fsoftware-trust-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicstudio%2Fsoftware-trust-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ceramicstudio","download_url":"https://codeload.github.com/ceramicstudio/software-trust-workshop/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicstudio%2Fsoftware-trust-workshop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34089329,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T02:42:55.910Z","updated_at":"2026-06-09T03:03:15.587Z","avatar_url":"https://github.com/ceramicstudio.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Software Trust System Workshop\nThis workshop has been planned to cover a few important topics relevant for the MetaMask and Karma3 Labs teams to get started building most efficiently and rapidly against their spec. In doing so, we have taken the [caip-261](https://github.com/dayksx/CAIPs/blob/main/CAIPs/caip-261.md) spec document into account.\n\nThe major themes we will cover are:\n\n1. Guided Ceramic node setup (using Ceramic-One, i.e. our Rust implementation)\n2. ComposeDB data modeling directly relevant to fulfill the spec\n3. Data interactions (querying, mutations, filtering)\n\n## Getting Started\n\nWe will be using this repository for part of our workshop, so let's first clone the codebase so we don't have to worry about it later (you will need npm installed):\n\n```bash\ngit clone https://github.com/ceramicstudio/software-trust-workshop \u0026\u0026 cd software-trust-workshop \u0026\u0026 npm install\n```\n\nYou can open this repository in your code editor of choice for the second half of this workshop. This implementation mimics the Kubo RPC API and relies on https://github.com/ceramicnetwork/js-ceramic for the remaining logic.\n\n## Node Setup\nThis workshop will use our [rust-ceramic](https://github.com/ceramicnetwork/rust-ceramic) implementation\n\nYou can find our external Rust-Ceramic instructions [here](https://threebox.notion.site/Ceramic-Recon-instructions-EXTERNAL-c2b93b2648d64cf0af0f4d2489d20399).\n\nAssuming we will all be operating on macs, open a new terminal and:\n\n1. Install nvm (if not already installed)\n\n```bash\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash\n```\n\n2. Select node v20:\n\n```bash\nnvm install v20\nnvm use 20\n```\n\n3. Install js-ceramic (with nighly builds):\n\n```bash\nnpm install --location=global @ceramicnetwork/cli@nightly\n```\n\n4. Download Rust-Ceramic from binary distribution:\n\n#### MacOS\n\n```bash\ncurl -LO https://github.com/ceramicnetwork/rust-ceramic/releases/download/v0.13.0/ceramic-one_aarch64-apple-darwin.tar.gz\ntar zxvf ceramic-one_aarch64-apple-darwin.tar.gz\n```\n\nOpen Finder, double click the `ceramic-one.pkg` file to start the install\n\n After installation, copy the binary:\n\n```bash\nsudo cp /Applications/ceramic-one /usr/local/bin/\n```\n\n#### Linux\n\n```bash\ncurl -LO https://github.com/ceramicnetwork/rust-ceramic/releases/download/v0.13.0/ceramic-one_x86_64-unknown-linux-gnu.tar.gz\ntar zxvf ceramic-one_x86_64-unknown-linux-gnu.tar.gz\ndpkg -i ceramic-one.deb\n```\n\n5. Start rust-ceramic (must be started first as js-ceramic needs the endpoint on startup):\n\n```bash\nceramic-one daemon\n```\n\n6. In a new terminal, start js-ceramic with `CERAMIC_RECON_MODE` enabled and pointing to the rust-ceramic instance:\n\n```bash\nnvm use 20\nCERAMIC_RECON_MODE=\"true\" ceramic daemon --ipfs-api http://localhost:5001\n```\n\nWe now have our Ceramic node running on port 7007. In the following steps, we will deploy our composite onto our node.\n\n**Troubleshooting** \n\nIf you received error messages, please first ensure that you're running node v20 in the terminals you're running the above commands in. Next, if you've previously ran Ceramic locally, the legacy data from those sessions might also be causing issues. Go into `~/.ceramic` from your machine's root directory (if exists) and delete the `statestore` subdirectory and the `indexing.sqlite` file before trying the commands again.\n\n### Configure Your Admin DID\n\nBack in your code editor's terminal (or a terminal opened to the `software-trust-workshop` directory), configure an admin DID our node will use in order to deploy our composites:\n\n```bash\nnvm use 20\nnpm install -g @composedb/cli\n```\n\n```bash\ncomposedb did:generate-private-key\n```\n\nThis will print out a unique and random private key. We need to get the public key associated with this private key.\n\nStore the private in an environment variable.\n\n```bash\nread -s DID_PRIVATE_KEY #paste in the key from the previous command and hit enter\nexport DID_PRIVATE_KEY\n```\n\nNow generate the corresponding public key from the private key:\n\n```bash\ncomposedb did:from-private-key\n```\n\nOn your machine, go into the following file off your machine's root: `~/.ceramic/daemon.config.json`. In your daemon.config.json file, add the did you just added into the `admin-dids` array. It should look something like this:\n\n```json\n{\n  \"anchor\": {},\n  \"http-api\": {\n    \"cors-allowed-origins\": [\n      \".*\"\n    ],\n    \"admin-dids\": [\"did:key:\u003cyour new public key\u003e\"]\n  },\n  \"ipfs\": {\n    \"mode\": \"bundled\"\n  },\n  \"logger\": {\n    \"log-level\": 2,\n    \"log-to-files\": false\n  },\n  \"network\": {\n    \"name\": \"testnet-clay\"\n  },\n  \"node\": {},\n  \"state-store\": {\n    \"mode\": \"fs\",\n    \"local-directory\": \"/Users/\u003cyou\u003e/.ceramic/statestore\"\n  },\n  \"indexing\": {\n    \"db\": \"sqlite:///Users/\u003cyou\u003e/.ceramic/indexing.sqlite\",\n    \"allow-queries-before-historical-sync\": true,\n    \"models\": []\n  }\n}\n```\n\nRestart the js-ceramic process for the new config to take effect - go back to the original two terminals you began with, cancel the processes that are running, and re-run steps 6 above (which restarts the js-ceramic process - you can keep the rust-ceramic process running).\n\n### Deploy the Models\n\nBack in the root directory of this repository you should now be able to deploy the existing composite we previously created previously by running:\n\n```bash\ncomposedb composite:deploy ./src/__generated__/definition.json\n```\n\nFinally, you can run a command (like the one below) to manually direct your new node to peer with another node running Rust-Ceramic that is also indexing the same models:\n\n```bash\ncurl -X POST \"http://localhost:5001/api/v0/swarm/connect?arg=/ip4/137.184.2.2/tcp/4001/p2p/12D3KooWJqb7KjjcWSC92xcSHhdGUrnJ5FJiTHHdZEW7QaLWG5X3\"\n```\n\n## Models Walk-Through\n\nIt was requested that we walk through the user-to-user trust signal data models during this workshop. To that end, here's a quick reminder of an example of the JSON schema mentioned in [capi-261](https://github.com/dayksx/CAIPs/blob/caips-split/CAIPs/caip-261.md):\n\n### User-to-User Signals\n\n```json\n\"@context\": [\"https://www.w3.org/2018/credentials/v1\"],\n\"type\": [\"VerifiableCredential\", \"PeerTrustCredential\"],\n\"issuanceDate\": \"2024-02-15T07:05:56.273Z\",\n\"issuer\": \"did:pkh:eip155:1:0x44dc4E3309B80eF7aBf41C7D0a68F0337a88F044\",\n\"credentialSubject\":\n{\n  \"id\": \"did:pkh:eip155:1:0xfA045B2F2A25ad0B7365010eaf9AC2Dd9905895c\",\n  \"trustworthiness\":\n  [\n    {\n      \"scope\": \"Honesty\",\n      \"level\": 0.5,\n      \"reason\": [\"Alumnus\"]\n    },\n    {\n      \"scope\": \"Software development\",\n      \"level\": 1,\n      \"reason\": [\"Software engineer\", \"Ethereum core developer\"]\n    },\n    {\n      \"scope\": \"Software security\",\n      \"level\": 0.5,\n      \"reason\": [\"White Hat\", \"Smart Contract Auditor\"]\n    }\n  ]\n},\n\"credentialSchema\": [{\n  \"id\": \"ipfs://QmcwYEnLysTyepjjtJw19oTDwuiopbCDbEcCuprCBiL7gl\",\n  \"type\": \"JsonSchema\"\n},\n\"proof\": {}\n```\n\nThe data model above is in JWT verifiable credential format. Here's how we are thinking about how this translates to ComposeDB:\n\n\n```GraphQL\n################## Account Trust Credentials\ntype AccountTrustSignal\n  @createModel(accountRelation: SET, accountRelationFields: [\"recipient\"], description: \"An account trust signal\")\n  @createIndex(fields: [{ path: \"recipient\" }])\n  @createIndex(fields: [{ path: \"issuanceDate\" }]) {\n  issuer: DID! @documentAccount\n  recipient: DID! @accountReference\n  issuanceDate: DateTime!\n  trustWorthiness: [AccountTrustTypes!]! @list(maxLength: 1000)\n  proof: String! @string(maxLength: 10000)\n}\n\ntype AccountTrustTypes {\n  scope: String! @string(maxLength: 1000)\n  level: Float! \n  reason: [String] @string(maxLength: 1000) @list(maxLength: 100)\n}\n```\n\n**Use of SET**\n\nIn ComposeDB, you can define relations between a schema model instance and the Ceramic account that controls that instance in 3 ways - SINGLE, LIST, AND SET. SINGLE requires there only ever be 1 model instance for that schema per account. LIST allows there to be an unlimited number of instances associated with that account. Conversely, SET allows developers to enforce a unique list of instances associated with an account based on a certain subfield (or set of subfields).\n\nIn the schema above, by indicating SET with the `accountRelationFields` array set to \"recipient\", we are ensuring that user A can only create 1 instance that points to user B.\n\nFor more information, read our [SET RFC](https://forum.ceramic.network/t/rfc-native-support-for-unique-list-relationships-between-stream-types-and-accounts/1406).\n\n**Use of @createIndex**\n\nThe @createIndex directive instructs your ComposeDB node to build an index on the defined field, allowing developers to perform filters and ordering based on those indexes. Note that this does NOT work for embedded objects or enums.\n\n**Issuer**\n\nAny field definition marked as `DID! @documentAccount` will be automatically filled based on the authenticated account creating or updating a model instance (meaning it does not need to be manually inputted).\n\n**Proof**\n\nThe plaintext fields in the `AccountTrustSignal` model provides all the information we need to read from directly. However, we've left in the \"proof\" field to accommodate any portable verified data object - for example, a stringified VC signed by the authenticated user.\n\n### User-to-Software Audit Signals\n\nOriginal JSON representation example:\n\n```json\n\"id\": \"ipfs://QmPTqvH3vm6qcZSGqAUsq78MQa9Ctb56afRZg1WJ5sKLiu\",\n\"type\": [\"VerifiableCredential\", \"SecurityReportCredential\"],\n\"issuanceDate\": \"2024-02-15T07:05:56.273Z\",\n\"issuer\": \"did:pkh:eth:0x44dc4E3309B80eF7aBf41C7D0a68F0337a88F044\",\n\"credentialSubject\":\n{\n  \"id\": \"snap://CLwZocaUEbDErtQAsybaudZDJq65a8AwlEFgkGUpmAQ=\",\n  \"securityStatus\": \"Unsecured\",\n  \"securityFindings\": [\n    {\n      \"criticality\": 1,\n      \"type\": \"Key leak\",\n      \"description\": \"`snap_getBip44Entropy` makes the parent key accessible\"\n      \"lang\": \"en\"\n    },\n    {\n      \"criticality\": 0.5,\n      \"type\": \"Buffer Overflow\"\n    },\n    {\n      \"criticality\": 0.25,\n      \"type\": \"Phishing\"\n    },\n    {\n      \"criticality\": 0,\n      \"type\": \"Data leak\",\n      \"description\": \"API can communicate data to a centralized server\"\n    },\n  ]\n},\n\"proof\": {}\n```\n\nComposeDB interpretation:\n\n```GraphQL\ntype SecurityAudit\n  @createModel(accountRelation: SET, accountRelationFields: [\"subjectId\"], description: \"A security audit\")\n  @createIndex(fields: [{ path: \"subjectId\" }])\n  @createIndex(fields: [{ path: \"issuanceDate\" }])\n  @createIndex(fields: [{ path: \"securityStatus\" }]) {\n  issuer: DID! @documentAccount\n  subjectId: String! @string(maxLength: 1000)\n  issuanceDate: DateTime!\n  securityStatus: Boolean!\n  securityFindings: [Findings!]! @list(maxLength: 1000)\n  proof: String! @immutable @string(maxLength: 10000)\n  reviews: [AuditReview] @relationFrom(model: \"AuditReview\", property: \"auditId\")\n}\n\ntype Findings {\n  criticality: Float! \n  type: String! @string(maxLength: 1000)\n  description: String @string(maxLength: 1000)\n  lang: String! @string(maxLength: 2)\n}\n```\n\n**Use of SET**\n\nIt's highly relevant here for us to limit 1 security audit an account can issue against any 1 software verion. In this case, we've represented the identifier for a snap as `subjectId` (for example, \"snap://CLwZocaUEbDErtQAsybaudZDJq65a8AwlEFgkGUpmAQ=\").\n\n**Use of @immutable**\n\nSince our system will allow users to generate reviews of audits, we want to ensure that the values of those audits don't change over time (thus nullifying reviews that point to those audits). It's unreasonable to expect users to constantly keep watch across audits they've left reviews for to ensure they don't change over time, so we want some assurances that they won't change.\n\nIn this case, since we're assuming that the `proof` subfield would contain a stringified signed object (like a VC), we've identified this field as immutable, which means it cannot change after it's been initially set. Given that there's a \"!\" following the scalar type (String), GraphQL will not allow any model instance creation query to go through without this field filled in.\n\nThis implementation would require some client-side work to verify that the VC matches the values in the ComposeDB fields. Another solution could enforce locking of the entire document, if desired.\n\n**Relation to AuditReview**\n\nIn the next section we will observe the `AuditReview` schema definition (which can only ever be created in relation to a specific `SecurityAudit` instance). This means that developers will be able to query based on the relationship between an `AuditReview` and the `SecurityAudit` it references, in addition to the opposite direction (`AuditReviews` associated with a specific `SecurityAudit`).\n\n### User-to-Audit Review Signals\n\nOriginal JSON representation example:\n\n```json\n\"type\": [\"VerifiableCredential\", \"ReviewCredential\"],\n\"issuanceDate\": \"2024-02-15T07:05:56.273Z\",\n\"issuer\": \"did:pkh:eth:0x44dc4E3309B80eF7aBf41C7D0a68F0337a88F044\",\n\"credentialSubject\":\n{\n  \"id\": \"ipfs://QmPTqvH3vm6qcZSGqAUsq78MQa9Ctb56afRZg1WJ5sKLiu\",\n  \"currentStatus\": \"Disputed\",\n  \"reason\": [\"Missed Vulnerability\"],\n},\n\"proof\": {}\n```\n\nComposeDB interpretation:\n\n```GraphQL\ntype AuditReview\n  @createModel(accountRelation: SET, accountRelationFields: [\"auditId\"], description: \"An audit review\")\n  @createIndex(fields: [{ path: \"auditId\" }])\n  @createIndex(fields: [{ path: \"endorsedStatus\" }]) {\n  issuer: DID! @documentAccount\n  issuanceDate: DateTime!\n  auditId: StreamID! @documentReference(model: \"SecurityAudit\")\n  audit: SecurityAudit! @relationDocument(property: \"auditId\")\n  endorsedStatus: Boolean!\n  reason: [String!]! @string(maxLength: 1000) @list(maxLength: 1000)\n  proof: String! @immutable @string(maxLength: 10000)\n}\n```\n\n**Use of SET**\n\nWe want to make sure an account can only ever create 1 review per specific audit.\n\n**Relation Back to SecurityAudit**\n\nWe've defined a relationship between the `AuditReview` and `SecurityAudit` schemas such that the `auditId` subfield must intake a Stream ID scalar that is a model instance document of a `SecurityAudit`. The `audit` subfield below it exposes that node for querying based on an `AuditReview` as the entrypoint. This is also what allows us to expose querying to all `AuditReview` instances from within a `SecurityAudit`.\n\n## Running Queries in the Application UI\n\nWe've set up this repository so you can run some queries to an existing production node that's indexing the models found in `/composites`, as well as running queries to your local node running on localhost:7007 (which you'll be able to toggle manually).\n\nFrom the root of this repository (`/software-trust-system`) start your application in developer mode:\n\n```bash\nnpm run dev\n```\n\nYou can now open your browser to http://localhost:3000/.\n\nUnder the \"ComposeDB Endpoint\" you will see that an external cloud node endpoint has already been provided to you. Go ahead and fill in the fields below. Once you've added at least 1 trustworthiness item, you should be able to issue a credential.\n\nGo ahead and issue a credential with the default node endpoint given to you. \n\nNavigate to `http://localhost:3000/reads` where we can verify that it synced to our node. With your local node still running, replace the value under the \"ComposeDB Endpoint\" text with `http://localhost:7007`. Next, if you run the default query within the `GetAccountTrustSignals` tab, you should be able to see the document you just created within the set of returned documents.\n\nFinally, you can navigate to `http://localhost:3000/mutations` to explore additional pre-made queries we've constructed across the other schemas.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceramicstudio%2Fsoftware-trust-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fceramicstudio%2Fsoftware-trust-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceramicstudio%2Fsoftware-trust-workshop/lists"}