Skip to content

feat: customer add command#40

Merged
alanshaw merged 3 commits into
mainfrom
ash/feat/customer-add
Jul 1, 2026
Merged

feat: customer add command#40
alanshaw merged 3 commits into
mainfrom
ash/feat/customer-add

Conversation

@alanshaw

Copy link
Copy Markdown
Member

An invocation that Hilt can make to insert a customer directly into the upload service DB.

Customers added by Hilt are "tenants" that are authenticated, authorized and billed by fil.one. However they need to be registered in the upload service to allow spaces to be provisioned and used.

Comment thread commands/customer/types.go Outdated
Comment on lines +8 to +9
// DID of the user account e.g. `did:mailto:agent`
Customer did.DID `cborgen:"customer" dagjsongen:"customer"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We must be careful here.

At the moment, FilOne Console assumes user=organization. We know we will need to support multi-user organisations in the future, FilOne Console's database model is a sort of half-way there.

In the context of libforge, we should make it clear whether Customer is "FilOne organization account" or "FilOne user account".

Since buckets are owned by organizations, not users, I am arguing that we should treat Customer as "organization account" here.

Suggested change
// DID of the user account e.g. `did:mailto:agent`
Customer did.DID `cborgen:"customer" dagjsongen:"customer"`
// DID of the FilOne organization account e.g. `did:mailto:agent`
Customer did.DID `cborgen:"customer" dagjsongen:"customer"`

But since I see Account below, perhaps I misunderstood the proposed data model. Is there any prior documentation describing how Forge components will use these customer records?

@alanshaw alanshaw Jun 29, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's fine. In Forge a customer is some entity that pays for storage. This tallies with an organization in Fil One.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(More context:) Contrasted with an account, which is some entity that represents (generally) a human user. Generally a customer is an account, but not necessarily the account that generally works with the space, or even necessarily has any capabilities on the space.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thank you for the explanation.

I still think we should change the comment to avoid confusion. How about the following?

Suggested change
// DID of the user account e.g. `did:mailto:agent`
Customer did.DID `cborgen:"customer" dagjsongen:"customer"`
// DID of the customer account e.g. `did:mailto:agent`
Customer did.DID `cborgen:"customer" dagjsongen:"customer"`

Comment thread commands/customer/types.go Outdated
Comment on lines +10 to +12
// Opaque identifier representing an account in the payment system
// e.g. Stripe customer ID (stripe:cus_9s6XKzkNRiz8i3)
Account *string `cborgen:"account,omitempty" dagjsongen:"account,omitempty"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a good idea to use Stripe customer ID here? FilOne is using orgId (a UUID generated by FilOne backend) as the unique identifier of the organisation account.

Conceptually, I believe each FilOne organization account has exactly one linked Stripe customer record.

In the current FilOne backend data model, Stripe customer ID is stored in BillingTable with pk: CUSTOMER#{userId}, sk: SUBSCRIPTION, I guess that's something we will need to change.

@pyropy is my understanding correct?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Fil One are the billing provider for the customer then I think it's appropriate to put the Fil One ID here (with an f1: prefix or something). This will come from Hilt, so it'll be what Hilt calls the tenant ID, which I assume is the same thing as the Fil One org ID.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Fil One are the billing provider for the customer then I think it's appropriate to put the Fil One ID here (with an f1: prefix or something). This will come from Hilt, so it'll be what Hilt calls the tenant ID, which I assume is the same thing as the Fil One org ID.

I like that!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest calling this field BillingAccountId or PaymentAccountId to make it clear this is not the same thing as the "Forge account" we are discussing in https://github.com/fil-forge/libforge/pull/40/changes#r3492457770

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, if this is going to contain FilOne OrgId, then I think ExternalAccountId captures the intent best.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine by me.

Comment thread commands/customer/types.go

type AddArguments struct {
// DID of the user account e.g. `did:mailto:agent`
Customer did.DID `cborgen:"customer" dagjsongen:"customer"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between Consumer (used by /provider/add command) and Customer?

Consumer did.DID `cborgen:"consumer" dagjsongen:"consumer"`

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A consumer is the space (bucket).

The (simplified) ER diagram looks like this:

erDiagram
    CUSTOMER ||--o{ SUBSCRIPTION : has
    SUBSCRIPTION ||--o{ CONSUMER : subscribes
    CUSTOMER {
        DID customer PK
        string account
    }
    SUBSCRIPTION {
        CID subscription PK
        DID customer FK
    }
    CONSUMER {
        DID consumer PK
        CID subscription FK
    }

Loading

In the CUSTOMER table, the account field is the ID in the external billing system for the customer. The ID of a customer has traditionally been a did:mailto:, but will be a did:plc: for Fil One orgs.

The SUBSCRIPTION table maps a customer paying for 1 or many spaces.

The CONSUMER table is the space(s) that will be billed for as part of the subscription.

The structure is designed so that moving spaces or groups of spaces between customers is easy.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the explanation and the diagram; it's super helpful!

I feel we are creating confusing vocabulary here.

In my understanding (which may be wrong, I am not a native English speaker): The terms "consumer" and "customer" are often used interchangeably, e.g. if I buy an iPhone with an iCloud subscription, I am both "customer" of Apple and also "consumer" of Apple services.

Claude's answer:

A customer buys something; a consumer uses or consumes it. They're often the same person, but not always.
The core distinction is the role in the transaction. A customer is whoever pays for a product or service. A consumer is whoever actually uses it. When a parent buys baby food, the parent is the customer and the baby is the consumer. When you buy dog food, you're the customer; your dog is the consumer.

Which seems to confirm my view - as a FilOne user, I am both the customer (paying for the service) and the consumer (using the storage service).

I can see how "consumer" may have been a good name before "customer" entered the picture, but I cannot help but find the new domain model very confusing.

Can we find a different name for the Consumer entity?

A few suggestions from Claude:

  • Space — This is what Alan literally calls it in prose ("A consumer is the space (bucket)"). It matches the domain vocabulary already used elsewhere ("spaces to be provisioned"). Clearest and most honest.
  • BillableSpace or SubscribedSpace — If you want to keep the emphasis that this is specifically the space as attached to a subscription for billing, rather than the space in general.
  • SubscriptionItem / SubscriptionEntry — If you want to foreground the relationship (what a subscription contains) rather than the resource itself. This mirrors Stripe's own subscription_item naming, which fits given the Stripe alignment discussed elsewhere in the PR. Downside: less concrete about what the item is.

I like Space most, with SubscribedSpace as an option if we want to emphasise the relation to subscriptions.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it is confusing. It also looks very similar to customer so easy to read incorrectly at a glance. I will create a follow up ticket to rename consumer since this is out of scope for this PR.

FWIW I think the original idea here was explicitly not to call it space to allow things that we do not consider to be "spaces" to be billed for.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Peeja Peeja left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with @bajtos's documentation suggestions to clarify the semantics

alanshaw added 2 commits July 1, 2026 17:21
Adds the S3 commands as specified in
fil-one/RFC#8

A few of the types have some hand-rolled CBOR/JSON encoders/decoders
since for some reason `cbor-gen` (and hence `dag-json-gen`) do not
support slices as map values.
@alanshaw alanshaw merged commit f0706e1 into main Jul 1, 2026
7 checks passed
@alanshaw alanshaw deleted the ash/feat/customer-add branch July 1, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants