I would love to see a world where ones twtxt feed is defined by webfinger. So @xuu@txt.sour.is => https://text.sour.is/user/xuu/twtxt.txt
Then my identity can exist independent of the feed location. And I can host multiple protocol types for my feed. Ie. http/gopher/Gemini/irc DCC/etc
A type of assumption that really grinds my gears: when someone expects a certain event will happen, and other people immediately assume that means the person wants the event to happen. Thinking something will happen and wanting it to happen are two very different things.
So you would have:
type ErrPermissionNotAllowed []Permission
func (perms ErrPermissionNotAllowed) Is(permission Permission) bool {
for _, p := range perms {
if p == permission { return true }
}
return false
}
var err error = errPermissionNotAllowed{"is-noob"}
if errors.Is(err, ErrPermissionNotAllowed{}) { ... } // user is not allowed
var e ErrPermissionNotAllowed
if errors.As(err, e) && e.Is("a-noob") { ... } // user is not allowed because they are a noob.
@lyse@lyse.isobeef.org do you need to have an explicit Is function? I believe errors.Is has reflect lite and can do the type infer for you. The Is is only really needed if you have a dynamic type. Or are matching a set of types as a single error maybe? The only required one would be Unwrap if your error contained some other base type so that Is/As can reach them in the stack.
As is perfect for your array type because it asserts the matching type out the wrap stack and populates the type for evaluating its contents.
An official FBI document dated January 2021, obtained by the American association “Property of People” through the Freedom of Information Act.

This document summarizes the possibilities for legal access to data from nine instant messaging services: iMessage, Line, Signal, Telegram, Threema, Viber, WeChat, WhatsApp and Wickr. For each software, different judicial methods are explored, such as subpoena, search warrant, active collection of communications metadata (“Pen Register”) or connection data retention law (“18 USC§2703”). Here, in essence, is the information the FBI says it can retrieve:
Apple iMessage: basic subscriber data; in the case of an iPhone user, investigators may be able to get their hands on message content if the user uses iCloud to synchronize iMessage messages or to back up data on their phone.
Line: account data (image, username, e-mail address, phone number, Line ID, creation date, usage data, etc.); if the user has not activated end-to-end encryption, investigators can retrieve the texts of exchanges over a seven-day period, but not other data (audio, video, images, location).
Signal: date and time of account creation and date of last connection.
Telegram: IP address and phone number for investigations into confirmed terrorists, otherwise nothing.
Threema: cryptographic fingerprint of phone number and e-mail address, push service tokens if used, public key, account creation date, last connection date.
Viber: account data and IP address used to create the account; investigators can also access message history (date, time, source, destination).
WeChat: basic data such as name, phone number, e-mail and IP address, but only for non-Chinese users.
WhatsApp: the targeted person’s basic data, address book and contacts who have the targeted person in their address book; it is possible to collect message metadata in real time (“Pen Register”); message content can be retrieved via iCloud backups.
Wickr: Date and time of account creation, types of terminal on which the application is installed, date of last connection, number of messages exchanged, external identifiers associated with the account (e-mail addresses, telephone numbers), avatar image, data linked to adding or deleting.
TL;DR Signal is the messaging system that provides the least information to investigators.
@mckinley@twtxt.net backintime for my desktop and work files. A combination of rsync, zfs snapshots, and redundancy for “at rest” type things.
On my blog: Normalizing Image Type and Size https://john.colagioia.net/blog/2023/04/05/file-type-size.html #linux #programming #techtips
So. Some bits.
i := fIndex(xs, 5.6)
Can also be
i := Index(xs, 5.6)
The compiler can infer the type automatically. Looks like you mention that later.
Also the infer is super smart.. You can define functions that take functions with generic types in the arguments. This can be useful for a generic value mapper for a repository
func Map[U,V any](rows []U, fn func(U) V) []V {
out := make([]V, len(rows))
for i := range rows { out = fn(rows[i]) }
return out
}
rows := []int{1,2,3}
out := Map(rows, func(v int) uint64 { return uint64(v) })
I am pretty sure the type parameters goes the other way with the type name first and constraint second.
func Foo[comparable T](xs T, s T) int
Should be
func Foo[T comparable](xs T, s T) int
so in effect it would look something like this:
---
subject: acct:me@sour.is
aliases:
- salty:me@sour.is
- yarn:xuu@ev.sour.is
- status:xuu@chaos.social
- mailto:me@sour.is
---
subject: salty:me@sour.is
aliases:
- acct:me@sour.is
links:
- rel: self
type: application/json+salty
href: https://ev.sour.is/inbox/01GAEMKXYJ4857JQP1MJGD61Z5
properties:
"http://salty.im/ns/nick": xuu
"http://salty.im/ns/display": Jon Lundy
"http://salty.im/ns/pubkey": kex140fwaena9t0mrgnjeare5zuknmmvl0vc7agqy5yr938vusxfh9ys34vd2p
---
subject: yarn:xuu@ev.sour.is
links:
- rel: https://txt.sour.is/user/xuu
properties:
"https://sour.is/rel/redirect": https://txt.sour.is/.well-known/webfinger?resource=acct%3Axuu%40txt.sour.is
---
subject: status:xuu@chaos.social
links:
- rel: http://joinmastodon.org#xuu%40chaos.social
properties:
"https://sour.is/rel/redirect": https://chaos.social/.well-known/webfinger?resource=acct%3Axuu%40chaos.social
---
subject: mailto:me@sour.is
...
@prologic@twtxt.net That was exactly my thought at first too. but what do we put as the rel for salty account? since it is decentralized we dont have a set URL for machines to key off. so for example take the standard response from okta:
# http GET https://example.okta.com/.well-known/webfinger resource==acct:bob
{
"links": [
{
"href": "https://example.okta.com/sso/idps/OKTA?login_hint=bob#",
"properties": {
"okta:idp:type": "OKTA"
},
"rel": "http://openid.net/specs/connect/1.0/issuer",
"titles": {
"und": "example"
}
}
],
"subject": "acct:bob"
}
It gives one link that follows the OpenID login. So the details are specific to the subject acct:bob.
Mastodons response:
{
"subject": "acct:xuu@chaos.social",
"aliases": [
"https://chaos.social/@xuu",
"https://chaos.social/users/xuu"
],
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://chaos.social/@xuu"
},
{
"rel": "self",
"type": "application/activity+json",
"href": "https://chaos.social/users/xuu"
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe"
}
]
}
it supplies a profile page and a self which are both specific to that account.
@abucci@anthony.buc.ci did you know about the chip inside USB-C cables?

https://connectorsupplier.com/usb-type-c-what-you-need-to-know/
some groups have created their own chips that have hidden keyloggers that can phone home over network connections.
@prologic@twtxt.net see where its used maybe that can help.
https://github.com/sour-is/ev/blob/main/app/peerfinder/http.go#L153
This is an upsert. So I pass a streamID which is like a globally unique id for the object. And then see how the type of the parameter in the function is used to infer the generic type. In the function it will create a new *Info and populate it from the datastore to pass to the function. The func will do its modifications and if it returns a nil error it will commit the changes.
The PA type contract ensures that the type fulfills the Aggregate interface and is a pointer to type at compile time.
one that i think is pretty interesting is building up dependent constraints. see here.. it accepts a type but requires the use of a pointer to type.
https://github.com/sour-is/ev/blob/main/pkg/es/es.go#L315-L325
$name$ and then dispatch the hashing or checking to its specific format.
Hold up now, that example hash doesn’t have a
$prefix!
Well for this there is the option for a hash type to set itself as a fall through if a matching hash doesn’t exist. This is good for legacy password types that don’t follow the convention.
func (p *plainPasswd) ApplyPasswd(passwd *passwd.Passwd) {
passwd.Register("plain", p)
passwd.SetFallthrough(p)
}
https://github.com/sour-is/go-passwd/blob/main/passwd_test.go#L28-L31
$name$ and then dispatch the hashing or checking to its specific format.
Here is an example of usage:
func Example() {
pass := "my_pass"
hash := "my_pass"
pwd := passwd.New(
&unix.MD5{}, // first is preferred type.
&plainPasswd{},
)
_, err := pwd.Passwd(pass, hash)
if err != nil {
fmt.Println("fail: ", err)
}
// Check if we want to update.
if !pwd.IsPreferred(hash) {
newHash, err := pwd.Passwd(pass, "")
if err != nil {
fmt.Println("fail: ", err)
}
fmt.Println("new hash:", newHash)
}
// Output:
// new hash: $1$81ed91e1131a3a5a50d8a68e8ef85fa0
}
This shows how one would set a preferred hashing type and if the current version of ones password is not the preferred type updates it to enhance the security of the hashed password when someone logs in.
https://github.com/sour-is/go-passwd/blob/main/passwd_test.go#L33-L59
I made a thing. Its a multi password type checker. Using the PHC string format we can identify a password hashing format from the prefix $name$ and then dispatch the hashing or checking to its specific format.
@carsten@yarn.zn80.net what type of NAS? I just upgraded my oooold (~2008) Drobo to a Synology. I have been impressed with all the neat stuff it can do.
@abucci@anthony.buc.ci Its not better than a Cat5e. I have had two versions of the device. The old ones were only 200Mbps i didn’t have the MAC issue but its like using an old 10baseT. The newer model can support 1Gbps on each port for a total bandwidth of 2Gbps.. i typically would see 400-500Mbps from my Wifi6 router. I am not sure if it was some type of internal timeout or being confused by switching between different wifi access points and seeing the mac on different sides.
Right now I have my wifi connected directly with a cat6e this gets me just under my providers 1.3G downlink. the only thing faster is plugging in directly.
MoCA is a good option, they have 2.5G models in the same price range as the 1G Powerline models BUT, only if you have the coax in wall already.. which puts you in the same spot if you don’t. You are for sure going to have an outlet in every room of the house by code.
Huh… Nope.
HTTP/1.1 200 OK
Content-Length: 407
Content-Type: text/calendar
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag
Permissions-Policy: interest-cohort=()
Content-Security-Policy: default-src 'none'; sandbox
Referrer-Policy: same-origin
Vary: Authorization
BEGIN:VCALENDAR
VERSION:2.0;2.0
PRODID:SandCal
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTAMP:20220822T180903Z
UID:bb63bfbd-623e-4805-b11b-3181d96375e6
DTSTART;TZID=America/Chicago:20220827T000000
CREATED:20220822T180903Z
LAST-MODIFIED:20220822T180903Z
LOCATION:https://meet.jit.si/Yarn.social
SUMMARY:Yarn Call
RRULE:FREQ=WEEKLY
DTEND;TZID=America/Chicago:20220827T010000
END:VEVENT
END:VCALENDAR
@prologic@twtxt.net odd is it maybe a wrong mime type thing? Should be text/calendar. Some http servers can mistakenly mark them application/octet-stream
@prologic@twtxt.net correct type parameters. 😅
(cont.)
Just to give some context on some of the components around the code structure.. I wrote this up around an earlier version of aggregate code. This generic bit simplifies things by removing the need of the Crud functions for each aggregate.
Domain ObjectsA domain object can be used as an aggregate by adding the event.AggregateRoot struct and finish implementing event.Aggregate. The AggregateRoot implements logic for adding events after they are either Raised by a command or Appended by the eventstore Load or service ApplyFn methods. It also tracks the uncommitted events that are saved using the eventstore Save method.
type User struct {
Identity string ```json:"identity"`
CreatedAt time.Time
event.AggregateRoot
}
// StreamID for the aggregate when stored or loaded from ES.
func (a *User) StreamID() string {
return "user-" + a.Identity
}
// ApplyEvent to the aggregate state.
func (a *User) ApplyEvent(lis ...event.Event) {
for _, e := range lis {
switch e := e.(type) {
case *UserCreated:
a.Identity = e.Identity
a.CreatedAt = e.EventMeta().CreatedDate
/* ... */
}
}
}
Events
Events are applied to the aggregate. They are defined by adding the event.Meta and implementing the getter/setters for event.Event
type UserCreated struct {
eventMeta event.Meta
Identity string
}
func (c *UserCreated) EventMeta() (m event.Meta) {
if c != nil {
m = c.eventMeta
}
return m
}
func (c *UserCreated) SetEventMeta(m event.Meta) {
if c != nil {
c.eventMeta = m
}
}
Reading Events from EventStore
With a domain object that implements the event.Aggregate the event store client can load events and apply them using the Load(ctx, agg) method.
// GetUser populates an user from event store.
func (rw *User) GetUser(ctx context.Context, userID string) (*domain.User, error) {
user := &domain.User{Identity: userID}
err := rw.es.Load(ctx, user)
if err != nil {
if err != nil {
if errors.Is(err, eventstore.ErrStreamNotFound) {
return user, ErrNotFound
}
return user, err
}
return nil, err
}
return user, err
}
OnX Commands
An OnX command will validate the state of the domain object can have the command performed on it. If it can be applied it raises the event using event.Raise() Otherwise it returns an error.
// OnCreate raises an UserCreated event to create the user.
// Note: The handler will check that the user does not already exsist.
func (a *User) OnCreate(identity string) error {
event.Raise(a, &UserCreated{Identity: identity})
return nil
}
// OnScored will attempt to score a task.
// If the task is not in a Created state it will fail.
func (a *Task) OnScored(taskID string, score int64, attributes Attributes) error {
if a.State != TaskStateCreated {
return fmt.Errorf("task expected created, got %s", a.State)
}
event.Raise(a, &TaskScored{TaskID: taskID, Attributes: attributes, Score: score})
return nil
}
Crud Operations for OnX Commands
The following functions in the aggregate service can be used to perform creation and updating of aggregates. The Update function will ensure the aggregate exists, where the Create is intended for non-existent aggregates. These can probably be combined into one function.
// Create is used when the stream does not yet exist.
func (rw *User) Create(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
session, err := rw.GetUser(ctx, identity)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, err
}
if err = fn(session); err != nil {
return nil, err
}
_, err = rw.es.Save(ctx, session)
return session, err
}
// Update is used when the stream already exists.
func (rw *User) Update(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
session, err := rw.GetUser(ctx, identity)
if err != nil {
return nil, err
}
if err = fn(session); err != nil {
return nil, err
}
_, err = rw.es.Save(ctx, session)
return session, err
}
Progress! so i have moved into working on aggregates. Which are a grouping of events that replayed on an object set the current state of the object. I came up with this little bit of generic wonder.
type PA[T any] interface {
event.Aggregate
*T
}
// Create uses fn to create a new aggregate and store in db.
func Create[A any, T PA[A]](ctx context.Context, es *EventStore, streamID string, fn func(context.Context, T) error) (agg T, err error) {
ctx, span := logz.Span(ctx)
defer span.End()
agg = new(A)
agg.SetStreamID(streamID)
if err = es.Load(ctx, agg); err != nil {
return
}
if err = event.NotExists(agg); err != nil {
return
}
if err = fn(ctx, agg); err != nil {
return
}
var i uint64
if i, err = es.Save(ctx, agg); err != nil {
return
}
span.AddEvent(fmt.Sprint("wrote events = ", i))
return
}
This lets me do something like this:
a, err := es.Create(ctx, r.es, streamID, func(ctx context.Context, agg *domain.SaltyUser) error {
return agg.OnUserRegister(nick, key)
})
I can tell the function the type being modified and returned using the function argument that is passed in. pretty cray cray.
Hi, I am playing with making an event sourcing database. Its super alpha but I thought I would share since others are talking about databases and such.
It’s super basic. Using tidwall/wal as the disk backing. The first use case I am playing with is an implementation of msgbus. I can post events to it and read them back in reverse order.

I plan to expand it to handle other event sourcing type things like aggregates and projections.
Find it here: sour-is/ev
@prologic@twtxt.net @movq@www.uninformativ.de @lyse@lyse.isobeef.org
I just went to type the phrase “I avoid Linux like the plague” but then remembered that we’ve all learned that most people won’t actually go much out of their way to avoid the plague.
On the blog: Amateur Stenography https://john.colagioia.net/blog/2021/11/28/steno.html #education #technology #opensource #typing
My nutritional supplements aim should be:
- 1 or 1.5 cups of lentils (or any beans you might like better).
- 2 or 2.5 cups of bitter greens.
- 1 cup of your favourite protein (or an egg), grilled, or fried with a little of olive oil.
- 1 or 2 tomatoes, or a handful if of the cherry type.
- No added sugars. If it is sweet, make it have fibre.
- No added salt (or very little and ionised), as salt is everywhere.
Related, I tried wild rice for the first time yesterday. It was different, in a good way.
@stackeffect@twtxt.stackeffect.de
now Apache also announces content-type: text/plain; charset=utf-8
Well, that fixed things. 🥳
@thewismit@twtxt.psynergy.io @jlj@twt.nfld.uk in old school terminal jargon the ^H means control H or the sequence used in some terminals to indicate backspace. The “joke” is that the term failed to interpret it correctly and you can see the partially typed word before they changed it.
@prologic@twtxt.net its the puny code for the yarn emoji. Though you would want the type-able version to redirect so its not hard to type on non mobile.
Are you able to coax your webserver to add the charset to the content type header? Browsers are having a hard time thinking you are sending latin-1
content-type: text/plain; charset=utf-8
@prologic@twtxt.net as promised! https://github.com/JonLundy/twtxt/blob/xuu/integrate-lextwt/types/lextwt/lextwt_test.go#
the lexer is nearing completion.. the tough part left is rooting out all the formatting code.
@prologic@twtxt.netd so.. convert the 4 attributes in the struct to private, add getters plus some the other methods that make sense.
type Twt interface {
Twter() Twter
Text() string
MarkdownText() string
Created() time.Time
...
}
@prologic@twtxt.net I have some ideas to improve on twtxt. figure I can contribute some. 😁 bit more work and it will almost be a drop in replacement for ParseFile
Kinda wish types.Twt was an interface. it’s sooo close.