Skip to main content

Platform Contracts for Builders designed

This page is the missing bridge between platform architecture and product implementation. It is for a team building a second product such as Token Factory, not for an app packager using the App SDK.

If a builder cannot tell what to import, what method to call, and what proof they must emit, the shared-platform docs are still too abstract.

What This Page Covers

NeedPackage anchorWhy it exists
authorize a product action against platform scopepackages/platform/iamone platform IAM decision model across products
meter product usage into the platform ledger pathpackages/platform/billing and packages/shared/eventsone accepted usage event path
append privileged mutation auditpackages/platform/auditone audit contract and custody model
record release, UAT, security, and rollout proofpackages/platform/evidenceone evidence bundle model

Minimal Builder Skeleton

The smallest second-product integration should look like this:

  1. define product id, scopes, usage units, audit actions, and evidence types;
  2. authorize every privileged or billable path through platform IAM;
  3. emit metered usage through the shared billing event model;
  4. append audit for privileged state change;
  5. record evidence bundle/items for release, UAT, and security proof.

IAM Contract

Use packages/platform/iam for platform-owned permission evaluation. The builder owns the product scope ids and role intent, but the decision surface is shared.

Important types and methods:

  • iam.ScopeDecisionInput
  • iam.Decision
  • iam.ActionPlatformAdmin
  • iam.ActionPlatformOpsRead
  • iam.ActionPlatformOpsWrite
  • (*iam.Service).EvaluateScopePermission
  • (*iam.Service).ResolvePlatformRoles
package tokenfactory

import (
"context"

"github.com/gpuaas/platform/packages/platform/iam"
)

func allowGatewayWrite(
ctx context.Context,
iamSvc *iam.Service,
actorID string,
roles []string,
) error {
decision := iamSvc.EvaluateScopePermission(ctx, iam.ScopeDecisionInput{
ActorType: iam.ActorTypeServiceAccount,
ActorID: actorID,
Roles: roles,
RequiredScope: "token_factory.gateway.write",
RequiredAction: iam.ActionPlatformOpsWrite,
AppliedScope: iam.ScopeProject,
})
if !decision.Allow {
return ErrPermissionDenied
}
return nil
}

Builder rule:

  • product code defines which scope id and role posture applies;
  • platform IAM decides whether the actor is allowed;
  • do not hardcode local “admin means allow” shortcuts inside the product.

Billing And Usage Contract

If the second product is billable, it should not write ledger rows directly. Emit accepted usage through events.UsageMeteredPayload and let platform billing consume it.

Important anchors:

  • billing.Service.RecordUsageMetered
  • billing.Service.ValidateUsageUnit
  • events.UsageMeteredPayload
  • events.SubjectBillingUsageMetered
package tokenfactory

import (
"context"
"time"

"github.com/gpuaas/platform/packages/platform/billing"
"github.com/gpuaas/platform/packages/shared/events"
)

func recordTokenUsage(
ctx context.Context,
billingSvc *billing.Service,
now time.Time,
) error {
payload := events.UsageMeteredPayload{
UsageEventID: "usage-evt-123",
UsageSource: "token_factory.gateway",
UsageUnit: "token_output",
ProductID: "token_factory",
OrgID: "org-123",
ProjectID: "project-456",
ResourceType: "model_request",
ResourceID: "req-789",
QuantityMillis: 1,
UnitCount: 2048,
BillFrom: now.Add(-1 * time.Minute).Format(time.RFC3339),
BillTo: now.Format(time.RFC3339),
IdempotencyKey: "token-factory-req-789-token_output",
RequestID: "req-789",
SourceCorrelationID: "corr-123",
Metadata: map[string]any{
"model_id": "llama-70b",
},
}
return billingSvc.RecordUsageMetered(ctx, payload)
}

Builder rule:

  • validate or register the usage unit first;
  • send one idempotent event per closed usage interval or accepted increment;
  • keep pricing, ledger, and invoice behavior platform-owned.

Audit Contract

Every privileged mutation must append a platform audit event. Product code owns the action meaning, but it must use a registered audit action and shared append surface.

Important anchors:

  • audit.AppendInput
  • (*audit.Service).Append
  • actor roles: user, admin, service_account, system
package tokenfactory

import (
"context"

"github.com/gpuaas/platform/packages/platform/audit"
)

func appendGatewayPolicyAudit(
ctx context.Context,
auditSvc *audit.Service,
orgID string,
userID string,
correlationID string,
) error {
_, err := auditSvc.Append(ctx, audit.AppendInput{
OrgID: &orgID,
ActorUserID: &userID,
ActorRole: audit.ActorRoleAdmin,
Action: "token_factory.policy.updated",
TargetType: "token_factory_policy",
Result: audit.ResultSuccess,
CorrelationID: correlationID,
Metadata: map[string]any{
"surface": "gateway-admin",
},
})
return err
}

Builder rule:

  • register the action in the audit-action registry before using it;
  • keep target_type stable and specific;
  • include correlation ids on every privileged mutation path.

Evidence Contract

Use packages/platform/evidence when the product needs release proof, UAT evidence, security controls evidence, or rollout evidence.

Important anchors:

  • evidence.CreateBundleInput
  • evidence.RecordItemInput
  • evidence.RecordInvariantInput
  • (*evidence.Service).CreateBundle
  • (*evidence.Service).RecordItem
  • (*evidence.Service).RecordInvariantCoverage
package tokenfactory

import (
"context"

"github.com/gpuaas/platform/packages/platform/evidence"
)

func recordReleaseProof(ctx context.Context, evidenceSvc *evidence.Service) error {
bundle, err := evidenceSvc.CreateBundle(ctx, evidence.CreateBundleInput{
SourceCommit: "abc123",
EnvironmentProfile: "dev",
ProductScope: "token_factory",
ChangeSummary: "gateway billing and IAM integration",
})
if err != nil {
return err
}

_, err = evidenceSvc.RecordItem(ctx, evidence.RecordItemInput{
BundleID: bundle.BundleID,
EvidenceType: "uat.summary",
Producer: "token-factory-uat",
Result: evidence.ResultPass,
Owner: "token-factory",
RetentionClass: "release",
Details: map[string]any{
"flow_id": "TOKEN-FACTORY-LAUNCH-001",
},
})
return err
}

Builder rule:

  • product code supplies the product-specific invariant ids and evidence details;
  • the bundle, result vocabulary, and custody model remain shared.

Contract Boundaries You Should Not Cross

Anti-patternWhy it is wrongCorrect pattern
product writes ledger rows directlybreaks rating and custody boundaryemit UsageMeteredPayload
product invents its own audit tablesplits privileged historyuse audit.Service.Append
product checks local admin flags without shared scope decisiondrifts IAM semantics across productscall iam.Service
product treats evidence as markdown-onlymakes release/UAT proof unreadable to toolingwrite evidence bundle/item records

Before You Start Coding

A second-product builder should be able to answer these four questions before the first handler lands:

  1. Which product-owned scopes and usage units must be registered first?
  2. Which privileged mutations require audit append?
  3. Which user/admin/operator flows produce billable usage?
  4. Which UAT or release flows must become evidence bundle items?

If those are not explicit, do onboarding and registry work first. Do not push that ambiguity into handler code.